• 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_iin[1,m] ai[1,m]的正整数序列权值之和。
    • x ≤ n ≤ 1 0 6 , k ≤ 1 0 8 xle nle10^6,kle10^8 xn106k108

    题解

    • 首先很重要的一步是拆贡献,$min(l-x,0)=sum_{i=x+1}^n [lge i] , 这 样 可 以 转 化 为 对 所 有 ,这样可以转化为对所有 iin(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=[jle 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 jge 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+ichoose 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(nlog_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
    ", ans);
    	return 0;
    }
    

    自我小结

    • 这题从头到尾很多处理方式都特别巧妙,同时也特别重要,如果自己从头开始一步一步推,会有豁然开朗的感觉。
    哈哈哈哈哈哈哈哈哈哈
  • 相关阅读:
    SQL语句大全
    网页常用小技巧
    卡通时钟代码
    舒服的颜色2
    静态代码
    Hibernate工作原理
    SQl多表查询优化 高效率SQL语句
    Hibernate API、对象状态、HQL、Criteria
    MySQL 学习笔记
    intramart知识
  • 原文地址:https://www.cnblogs.com/LZA119/p/14608422.html
Copyright © 2020-2023  润新知