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(lx,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 xn106k108

题解

  • 首先很重要的一步是拆贡献, m i n ( l − x , 0 ) = ∑ i = x + 1 n [ l ≥ i ] min(l-x,0)=\sum_{i=x+1}^n [l\ge i] min(lx,0)=i=x+1n[li],这样可以转化为对所有 i ∈ ( x , n ] i\in(x,n] i(x,n],求最长段长度 ≥ i \ge i 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=[ji]m+k<min(i,j)fjk(m1)
  • 含义为每次枚举当前最后一个连续段,长度 k k k取值为 [ 1 , i ) [1,i) [1,i),其中当 j − k > 0 j-k>0 jk>0 j > k j>k j>k时,转移系数为 m − 1 m-1 m1,因为要和前一种数不同;当 j − k = 0 j-k=0 jk=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=fj1m+1
  • j ≥ i j\ge i ji时, f j = f j − 1 ∗ m − f j − i ∗ ( m − 1 ) f_j=f_{j-1}*m-f_{j-i}*(m-1) fj=fj1mfji(m1)
  • 然后最后的总数由 f n f_n fn变为 f n − f n − 1 f_n-f_{n-1} fnfn1,因为现在的 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 m1,最后走到 n n n的方案数减去走到 n − 1 n-1 n1的方案数。
  • 由于起点太多,不妨反过来,变为以 0 0 0为起点走到 ( n − i , n ] (n-i,n] (ni,n]的方案数减去以 1 1 1为起点走到 ( n − i , n ] (n-i,n] (ni,n]的方案数,把起点统一,后者相当于以 0 0 0为起点走到 [ n − i , n − 1 ] [n-i,n-1] [ni,n1]的方案数,二者作差后只剩到 n n n的方案数减去到 n − i n-i ni的方案数,也就是只用求出这两个值再相减。
  • 至于怎么求,就很容易了,发现移动的方法有两种,分别为增加 1 1 1和增加 i i i,那么枚举后者的转移次数 j j j,对应的方案为 ( n − i j + i j ) n-ij+i\choose j (jnij+i),相当于总次数中选出 j j j次( i , j i,j i,j确定后总次数是确定的),贡献为方案数乘上 m n − i j ( 1 − m ) j m^{n-ij}(1-m)^j mnij(1m)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;
}

自我小结

  • 这题从头到尾很多处理方式都特别巧妙,同时也特别重要,如果自己从头开始一步一步推,会有豁然开朗的感觉。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值