JZOJ 7039. 2021.04.01【2021省赛模拟】计数(推式子+DP)
JZOJ 7039. 2021.04.01【2021省赛模拟】计数
题目大意
- 给出 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_i\in[1,m] ai∈[1,m]的正整数序列权值之和。
- x ≤ n ≤ 1 0 6 , k ≤ 1 0 8 x\le n\le10^6,k\le10^8 x≤n≤106,k≤108
题解
- 首先很重要的一步是拆贡献,$min(l-x,0)=\sum_{i=x+1}^n [l\ge i] , 这 样 可 以 转 化 为 对 所 有 ,这样可以转化为对所有 ,这样可以转化为对所有i\in(x,n] , 求 最 长 段 长 度 ,求最长段长度 ,求最长段长度\ge i$的序列个数。
- 接下来考虑到“最长段”这一限制若使用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=[j\le 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 j\ge 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+i\choose 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(n\log_2 n) O(nlog2n)级别的。
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace 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) return 1;
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;
}
int main() {
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\n", ans);
return 0;
}
自我小结
- 这题从头到尾很多处理方式都特别巧妙,同时也特别重要,如果自己从头开始一步一步推,会有豁然开朗的感觉。
哈哈哈哈哈哈哈哈哈哈

浙公网安备 33010602011771号