给出
n
,
m
,
x
n,m,x
n,m,x,定义一个序列的权值为
m
i
n
(
l
−
x
,
0
)
min(l-x,0)
min(l−x,0),其中
l
l
l为最长连续段的长度。求所有长度为
n
n
n且满足
a
i
∈
[
1
,
m
]
a_iin[1,m]
ai∈[1,m]的正整数序列权值之和。
x
≤
n
≤
1
0
6
,
k
≤
1
0
8
xle nle10^6,kle10^8
x≤n≤106,k≤108
接下来考虑到“最长段”这一限制若使用DP统计的话,需要一维
0
/
1
0/1
0/1记录是否存在过长度为
i
i
i的连续段,如果考虑反过来,求
<
i
<i
<i的序列,则不需要记录,因为必须保证所有段都小于。这样每次枚举
i
i
i都用总方案
m
n
m^n
mn减去算出的总数即可。
具体地,转移方程如下:
f
0
=
1
f_0=1
f0=1
f
j
=
[
j
≤
i
]
∗
m
+
∑
k
<
m
i
n
(
i
,
j
)
f
j
−
k
∗
(
m
−
1
)
f_j=[jle i]*m+sum_{k<min(i,j)} f_{j-k}*(m-1)
fj=[j≤i]∗m+∑k<min(i,j)fj−k∗(m−1)
含义为每次枚举当前最后一个连续段,长度
k
k
k取值为
[
1
,
i
)
[1,i)
[1,i),其中当
j
−
k
>
0
j-k>0
j−k>0即
j
>
k
j>k
j>k时,转移系数为
m
−
1
m-1
m−1,因为要和前一种数不同;当
j
−
k
=
0
j-k=0
j−k=0即
j
=
k
j=k
j=k时,转移系数为
m
m
m,因为这是第一段,任意一种数都可以取。
这样做是
O
(
n
3
)
O(n^3)
O(n3)的,用前缀和可以优化到
O
(
n
2
)
O(n^2)
O(n2)。
此时关键又来了,若用另一数组
s
s
s记录前缀和的话,转移式便无法继续优化了,若直接用
f
f
f记下前缀和,则式子化简后可以得到:
f
0
=
1
f_0=1
f0=1
j
<
i
j<i
j<i时,
f
j
=
f
j
−
1
∗
m
+
1
f_j=f_{j-1}*m+1
fj=fj−1∗m+1
j
≥
i
jge i
j≥i时,
f
j
=
f
j
−
1
∗
m
−
f
j
−
i
∗
(
m
−
1
)
f_j=f_{j-1}*m-f_{j-i}*(m-1)
fj=fj−1∗m−fj−i∗(m−1)
然后最后的总数由
f
n
f_n
fn变为
f
n
−
f
n
−
1
f_n-f_{n-1}
fn−fn−1,因为现在的
f
f
f是之前的
f
f
f的前缀和。
考虑转移式的意义,相当于以
[
0
,
i
)
[0,i)
[0,i)中任意一点为起点,每次可以向
j
+
1
j+1
j+1走去,方案为
m
m
m,或向
j
+
i
j+i
j+i走去,方案为
m
−
1
m-1
m−1,最后走到
n
n
n的方案数减去走到
n
−
1
n-1
n−1的方案数。
由于起点太多,不妨反过来,变为以
0
0
0为起点走到
(
n
−
i
,
n
]
(n-i,n]
(n−i,n]的方案数减去以
1
1
1为起点走到
(
n
−
i
,
n
]
(n-i,n]
(n−i,n]的方案数,把起点统一,后者相当于以
0
0
0为起点走到
[
n
−
i
,
n
−
1
]
[n-i,n-1]
[n−i,n−1]的方案数,二者作差后只剩到
n
n
n的方案数减去到
n
−
i
n-i
n−i的方案数,也就是只用求出这两个值再相减。
至于怎么求,就很容易了,发现移动的方法有两种,分别为增加
1
1
1和增加
i
i
i,那么枚举后者的转移次数
j
j
j,对应的方案为
(
n
−
i
j
+
i
j
)
n-ij+ichoose j
(jn−ij+i),相当于总次数中选出
j
j
j次(
i
,
j
i,j
i,j确定后总次数是确定的),贡献为方案数乘上
m
n
−
i
j
(
1
−
m
)
j
m^{n-ij}(1-m)^j
mn−ij(1−m)j。
对每个
i
i
i枚举次数只有
n
i
frac{n}{i}
in,总复杂度
O
(
n
log
2
n
)
O(nlog_2 n)
O(nlog2n)级别的。
代码
#include<cstdio>#include<cstring>#include<algorithm>usingnamespace std;#define N 1000010#define ll long long#define md 998244353
ll f[N], g[N], p[N], q[N];
ll C(int x,int y){return f[x]* g[y]% md * g[x - y]% md;}
ll ksm(ll x, ll y){if(!y)return1;
ll l =ksm(x, y /2);if(y %2)return l * l % md * x % md;return l * l % md;}
ll solve(int n,int x){
ll s =0;for(int i =0; i * x <= n; i++) s =(s +C(n - i * x + i, i)* p[i]% md * q[n - i * x]% md)% md;return s;}intmain(){int n, m, X, i;scanf("%d%d%d",&n,&m,&X);
p[0]= q[0]= f[0]=1;for(i =1; i <= n; i++) p[i]= p[i -1]*(1- m + md)% md, q[i]= q[i -1]* m % md;for(i =1; i <= n; i++) f[i]= f[i -1]* i % md;
g[n]=ksm(f[n], md -2);for(i = n -1; i >=0; i--) g[i]= g[i +1]*(i +1)% md;
ll ans =0;for(i = X +1; i <= n; i++) ans =(ans + q[n]-solve(n, i)+solve(n - i, i)+ md)% md;printf("%lld
", ans);return0;}