Edu Codeforce133 (D、F) DP、组合数学

D题 Chip Move

题意:有一条数轴,初始位于 0 0 0位置,给定整数 n , k ( 1 ≤ k , n ≤ 2 ⋅ 1 0 5 ) n,k(1\le k,n\le 2·10^5) n,k(1k,n2105),第 i i i次的移动距离仅能为 k + i k+i k+i的倍数,对于每个正整数 x x x,求走到 x x x这个点的方案数,其中 x ∈ [ 1 , n ] x\in[1,n] x[1,n]

  • 为了方便陈述,若当前轮数要求的走的长度为 k + i k+i k+i的倍数,我们称其为第 k + i k+i k+i轮,而非第 i i i

  • 先考虑一个最暴力的做法,设状态 d p [ i ] [ j ] dp[i][j] dp[i][j]表示,表示走 1 , 2 , ⋯   , i 1,2,\cdots,i 1,2,,i步能到位置 j j j的方案数的总和,显然初始状态位于 0 0 0位置,仅一种方案,即 d p [ k − 1 ] [ 0 ] = 1 dp[k-1][0]=1 dp[k1][0]=1

  • 考虑当前位置 j j j,逆向思维枚举上一步走的距离为 i i i的倍数,也就是反向跳 d × i d\times i d×i,其中 j − d × i ≥ 0 j-d\times i\ge 0 jd×i0,故 1 ≤ d ≤ ⌊ j i ⌋ 1\le d \le \lfloor \frac{j}{i}\rfloor 1dij的容易得到转移方程 d p [ i ] [ j ] = ∑ d = 1 ⌊ j i ⌋ d p [ i − 1 ] [ j − d × i ] dp[i][j]=\sum_{d=1}^{\lfloor \frac{j}{i}\rfloor} dp[i-1][j-d\times i] dp[i][j]=d=1ijdp[i1][jd×i]

  • 此时我们对于每轮每个位置的状态转移,最终需要 O ( n 3 ) O(n^3) O(n3)的时间复杂度

  • 往前回跳距离 i i i,我们发现对于 j − i j-i ji的状态转移方程为 d p [ i ] [ j − i ] = ∑ d = 1 ⌊ j − i i ⌋ d p [ i − 1 ] [ j − i − d × i ] dp[i][j-i]=\sum_{d=1}^{\lfloor \frac{j-i}{i}\rfloor} dp[i-1][j-i-d\times i] dp[i][ji]=d=1ijidp[i1][jid×i]

  • 对比上下两个转移方程,我们发现方程右部仅相差一个数 d p [ i ] [ j − i ] dp[i][j-i] dp[i][ji],故我们可以用一个前缀和来优化这个转移,即对于一个同余系 j   m o d   i j \bmod i jmodi,对应一个前缀和 t p [ j   m o d   i ] tp[j \bmod i] tp[jmodi],然后每轮预处理一下这个同余前缀和,即可将时间复杂度降为 O ( n 2 ) O(n^2) O(n2)

    此处类似完全背包问题同余系优化的思路

  • 但是我们发现,此时的时间复杂度,对于 n n n的范围来说,还是不能接受的。于是我们再到题意中寻找某些性质,祈求能找到数据的某些特殊性质

  • 有如下性质:题目中的轮数,最多仅有 O ( n ) O(\sqrt n) O(n )轮,即外层循环 i i i最大仅为 n \sqrt n n ,考虑最差情况 k = 1 k=1 k=1,每次跳的倍数 d = 1 d=1 d=1时,走了 i i i轮的情况,最后的距离为等差数列求和 [ 1 + ( 1 + i ) ] i 2 \cfrac{[1+(1+i)]i}{2} 2[1+(1+i)]i,发现其为 O ( n 2 ) O(n^2) O(n2)级别,即走的距离随着轮数,呈至少 Ω ( n 2 ) \Omega(n^2) Ω(n2)级别增长,故走完距离为 n n n也只需要 O ( n ) O(\sqrt n) O(n )

  • 这样我们就将时间复杂度降为 O ( n n ) O(n\sqrt n) O(nn )了,足矣AC此题

  • 最后考虑空间复杂度,我们发现第 i i i轮的状态仅跟第 i − 1 i-1 i1轮的有关,故可以使用滚动数组优化掉一维,这样我们的空间复杂度就可以降到 O ( n ) O(n) O(n),我们用 l a s t last last数组表示上一轮的状态

//其中Z表示模数类,运算时自动取余998244353,可参照jiangly代码
#define rep(i, a, b) for (int i = (a); i <= (b); i++)
int main()
{
    int n, k;
    cin >> n >> k;
    vector<Z> last(n + 1);
    last[0] = 1;
    vector<Z> ans(n + 1);
    rep(i, k, n)
    {
        vector<Z> dp(n + 1);
        vector<Z> tp(i);
        rep(j, i, n)
        {
            if(j - i >= 0)
                tp[j % i] += last[j - i];//同余系
            dp[j] = tp[j % i];
        }
        rep(j, 1, n)
            ans[j] += dp[j];
        if(all_of(dp.begin(), dp.end(), [&](const Z &a){ //是否本轮全部状态都为0
            return !a.val();
        })) break;
        last = dp; //滚动数组优化
    }
    rep(i, 1, n)
        cout << ans[i] << ' ';
}

F题 Bags with Balls

题意

给定 n ( 1 ≤ n ≤ 998244352 ) n(1\le n \le 998244352) n(1n998244352)个袋子,每个袋子中都有 m ( 1 ≤ m ≤ 998244352 ) m(1\le m \le 998244352) m(1m998244352)个球,且其中每个袋子中的球都由 1 ∼ m 1\sim m 1m编号。从每个袋子中各取出一个球,最终会得到 n n n个球,在这些球中,设奇数球的个数为 F F F。求解所有可能方案得到的 F k ( 1 ≤ k ≤ 2000 ) F^k(1\le k \le 2000) Fk(1k2000)的总和。

输入

T ( 1 ≤ T ≤ 5000 ) T(1\le T \le5000) T(1T5000)组数据,每组读入三个数 n , m , k ( 1 ≤ n , m ≤ 9982443521 ≤ k ≤ 2000 ) . n,m,k(1≤n,m≤9982443521≤k≤2000). n,m,k(1n,m9982443521k2000).

输出

如题意所述,对答案取余 998244353 998244353 998244353

  • 容易得到,一个袋子中,奇数球的个数为 ⌊ m + 1 2 ⌋ \lfloor \cfrac{m+1}{2}\rfloor 2m+1,偶数球的个数为 ⌊ m 2 ⌋ \lfloor \cfrac{m}{2}\rfloor 2m

  • 考虑最裸的做法,有如下生成函数: ( ⌊ m 2 ⌋ + ⌊ m + 1 2 ⌋ x ) n (\lfloor \cfrac{m}{2}\rfloor+\lfloor \cfrac{m+1}{2}\rfloor x)^n (⌊2m+2m+1x)n

  • 二项式定理对生成函数进行化简有 ∑ i = 1 n C n i ⌊ m 2 ⌋ n − i ⌊ m + 1 2 ⌋ i x i \sum_{i=1}^n C_n^i\lfloor \cfrac{m}{2}\rfloor^{n-i}\lfloor \cfrac{m+1}{2}\rfloor^i x^i i=1nCni2mni2m+1ixi,此处我省略掉了 i = 0 i=0 i=0的情况,因为 x x x的系数为 i i i表示当前取了 i i i个奇数编号的球,显然取 0 0 0个奇数编号的球不会对我们答案有任何贡献,前面的系数表示的为方案数,故我们所求的答案为 ∑ i = 1 n C n i ⌊ m 2 ⌋ n − i ⌊ m + 1 2 ⌋ i i k \sum_{i=1}^n C_n^i\lfloor \cfrac{m}{2}\rfloor^{n-i}\lfloor \cfrac{m+1}{2}\rfloor^i i^k i=1nCni2mni2m+1iik

  • 考虑 n , m , k n,m,k n,m,k的范围,发现 n , m , k ( 1 ≤ n , m ≤ 9982443521 ≤ k ≤ 2000 ) n,m,k(1≤n,m≤9982443521≤k≤2000) n,m,k(1n,m9982443521k2000)。我们无法在 O ( n ) O(n) O(n)的时间内解决这个问题,所以我们尽量需要往 O ( k ) O(k) O(k)的方向靠

  • 考虑对 i k i^k ik进行展开,对于第二类斯特林数(斯特林子集数),根据二项式反演有如下结论: i k = ∑ c = 0 i S 2 ( k , c ) ( i c ) c ! i^{k}=\sum_{c=0}^{i} S_{2}(k, c)\binom{i}{c}c! ik=c=0iS2(k,c)(ci)c!

    • 第二类斯特林数 S 2 ( n , m ) S_2(n,m) S2(n,m)的含义为,将 n n n个数,划分为 m m m个集合的方案数
    • 考虑动态规划,对于一个新来的数,有两种方案,一种是已有 m m m个集合,从 m m m个集合中选择一个加入,另一种是这个数单独创建一个集合
    • 可以得到状态转移方程 S 2 ( n , m ) = S 2 ( n − 1 , m − 1 ) + S 2 ( n − 1 , m ) × m S_2(n,m)=S_2(n-1,m-1)+S_2(n-1,m)\times m S2(n,m)=S2(n1,m1)+S2(n1,m)×m
    • 在本题中,预处理第二类斯特林数的时间复杂度为 O ( k 2 ) O(k^2) O(k2)

下列推导中, ( n i ) \binom{n}{i} (in)等价 C n i C_n^i Cni

  • 代入原式中可以得到

    ∑ i = 1 n ( n i ) ( ⌊ m 2 ⌋ ) n − i ( ⌊ m + 1 2 ⌋ ) i i k = ∑ i = 1 n ( n i ) ( ⌊ m 2 ⌋ ) n − i ( ⌊ m + 1 2 ⌋ ) i ( ∑ c = 0 i S 2 ( k , c ) ( i c ) c ! ) \begin{aligned} & \sum_{i=1}^n \binom{n}{i}(\lfloor \cfrac{m}{2}\rfloor)^{n-i} (\lfloor \cfrac{m+1}{2}\rfloor)^{i} i^k \\ = &\sum_{i=1}^n \binom{n}{i}(\lfloor \cfrac{m}{2}\rfloor)^{n-i} (\lfloor \cfrac{m+1}{2}\rfloor)^{i} (\sum_{c=0}^{i} S_2(k,c) \binom{i}{c} c!) \\ \end{aligned} =i=1n(in)(⌊2m)ni(⌊2m+1)iiki=1n(in)(⌊2m)ni(⌊2m+1)i(c=0iS2(k,c)(ci)c!)

  • 然后我们尝试交换一下求和顺序,思路同莫比乌斯反演一样,先枚举出内层循环可能出现的值,然后对于每个内层循环,列出对应的外层循环的值

    此处为了方便化简计算又加上 0 0 0这个方案,因为斯特林数 S [ i ] [ 0 ] = 0 S[i][0]=0 S[i][0]=0故不影响,此处仅为了方便推导

    ∑ c = 0 min ⁡ ( k , n ) ∑ i = c n ( n i ) ⌊ m 2 ⌋ n − i ⌊ m + 1 2 ⌋ i S 2 ( k , c ) ( i c ) c ! = ∑ c = 0 min ⁡ ( k , n ) S 2 ( k , c ) ( n c ) c ! ∑ i = c n ( n − c i − c ) ⌊ m 2 ⌋ n − i ⌊ m + 1 2 ⌋ i = ∑ c = 0 min ⁡ ( k , n ) S 2 ( k , c ) ( n c ) c ! ⌊ m + 1 2 ⌋ c ∑ i = c n ( n − c i − c ) ⌊ m 2 ⌋ n − i ⌊ m + 1 2 ⌋ i − c \begin{aligned} &\sum_{c=0}^{\min(k,n)} \sum_{i=c}^{n} \binom{n}{i}\lfloor \cfrac{m}{2}\rfloor^{n-i} \lfloor \cfrac{m+1}{2}\rfloor^{i} S_2(k,c) \binom{i}{c} c! \\ =&\sum_{c=0}^{\min(k,n)} S_2(k,c)\binom{n}{c} c!\sum_{i=c}^{n} \binom{n-c}{i-c} \lfloor \cfrac{m}{2}\rfloor^{n-i} \lfloor \cfrac{m+1}{2}\rfloor^{i}\\ =&\sum_{c=0}^{\min(k,n)} S_2(k,c)\binom{n}{c} c! \lfloor \cfrac{m+1}{2}\rfloor^c\sum_{i=c}^{n} \binom{n-c}{i-c} \lfloor \cfrac{m}{2}\rfloor^{n-i} \lfloor \cfrac{m+1}{2}\rfloor^{i-c} \end{aligned} ==c=0min(k,n)i=cn(in)2mni2m+1iS2(k,c)(ci)c!c=0min(k,n)S2(k,c)(cn)c!i=cn(icnc)2mni2m+1ic=0min(k,n)S2(k,c)(cn)c!2m+1ci=cn(icnc)2mni2m+1ic

  • 接下来单独处理 ∑ i = c n ( n − c i − c ) ⌊ m 2 ⌋ n − i ⌊ m + 1 2 ⌋ i − c \sum_{i=c}^{n} \binom{n-c}{i-c} \lfloor \cfrac{m}{2}\rfloor^{n-i} \lfloor \cfrac{m+1}{2}\rfloor^{i-c} i=cn(icnc)2mni2m+1ic

    • 使用 i = i − c i=i-c i=ic换元,求和范围从 [ c , n ] [c,n] [c,n]变为 [ 0 , n − c ] [0,n-c] [0,nc]

    • 得到 ∑ i = 0 n − c ( n − c i ) ⌊ m 2 ⌋ n − c − i ⌊ m + 1 2 ⌋ i \sum_{i=0}^{n-c} \binom{n-c}{i} \lfloor \cfrac{m}{2}\rfloor^{n-c-i} \lfloor \cfrac{m+1}{2}\rfloor^{i} i=0nc(inc)2mnci2m+1i

    • 发现刚好为一个二项式展开的式子,使用二项式定理反推回去得到
      ∑ i = 0 n − c ( n − c i ) ⌊ m 2 ⌋ n − c − i ⌊ m + 1 2 ⌋ i = ( ⌊ m 2 ⌋ + ⌊ m + 1 2 ⌋ ) n − c = m n − c \begin{align} &\quad \sum_{i=0}^{n-c} \binom{n-c}{i} \lfloor \cfrac{m}{2}\rfloor^{n-c-i} \lfloor \cfrac{m+1}{2}\rfloor^{i}\\ &=(\lfloor \cfrac{m}{2}\rfloor + \lfloor \cfrac{m+1}{2}\rfloor)^{n-c}\\ &=m^{n-c} \end{align} i=0nc(inc)2mnci2m+1i=(⌊2m+2m+1)nc=mnc

  • 故原式等于

    ∑ c = 0 min ⁡ ( k , n ) S 2 ( k , c ) ( n c ) c ! ⌊ m + 1 2 ⌋ c ∑ i = c n ( n − c i − c ) ⌊ m 2 ⌋ c ( m + 1 2 ) i − c = ∑ c = 0 min ⁡ ( k , n ) S 2 ( k , c ) ( n c ) c ! ⌊ m + 1 2 ⌋ c m n − c \begin{aligned} &\sum_{c=0}^{\min(k,n)} S_2(k,c)\binom{n}{c} c! \lfloor \cfrac{m+1}{2}\rfloor ^c\sum_{i=c}^{n} \binom{n-c}{i-c} \lfloor \cfrac{m}{2}\rfloor ^c(\frac{m+1}{2})^{i-c}\\ =&\sum_{c=0}^{\min(k,n)} S_2(k,c)\binom{n}{c} c! \lfloor \cfrac{m+1}{2}\rfloor ^c m^{n-c} \end{aligned} =c=0min(k,n)S2(k,c)(cn)c!2m+1ci=cn(icnc)2mc(2m+1)icc=0min(k,n)S2(k,c)(cn)c!2m+1cmnc

写代码时,可使用 ( n c ) c ! = n ! ( n − c ) ! \binom{n}{c}c!=\cfrac{n!}{(n-c)!} (cn)c!=(nc)!n!

  • 然后我们便可以在 O ( m i n ( k , n ) ) = O ( 能过 ) O(min(k,n))=O(能过) O(min(k,n))=O(能过)的时间没计算出这个式子的值
//其中Z表示模数类,运算时自动取余998244353,可参照jiangly代码

#define rep(i, a, b) for (int i = (a); i <= (b); i++)
Z S[K][K];
void init()
{//预处理斯特林数
    S[0][0] = 1;
    rep(i, 1, 2000)
        rep(j, 1, 2000)
            S[i][j] = S[i - 1][j - 1] + S[i - 1][j] * Z(j);
}

int main()
{
    init();
    cin >> T;
    while (T--)
    {
        int n, m, k;
        cin >> n >> m >> k;

        //用于计算 [(m + 1) / 2] ^ c
        Z t = Z((m + 1) / 2); // 奇数的个数
        Z tpow = 1;

        //用于计算 组合数 * c!,即 n! / (n - c)! 
        Z C = 1;

        //用于计算 m ^ (n - c)
        Z p = power(Z(m), n);
        Z invm = Z(1) / Z(m);

        Z ans = 0;
        rep(c, 1, k)
        {
            if(c > n)
                break;
            tpow *= t; // [(m + 1) / 2] ^ c
            C *= Z(n + 1 - c); // 组合数 * c!,即 n! / (n - c)! 
            p *= invm; // m ^ (n - c)
            Z tmp = S[k][c] * C * tpow * p;
            ans += tmp;
        }
        cout << ans << el;
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值