zoukankan      html  css  js  c++  java
  • CF1228E Another Filling the Grid

    因为要保证每行都必须填一个 (1),于是我们需要逐层考虑并在这层填若干个 (1),为了能描述当前的状态,我们可以令 (dp_{i, j}) 表示当前已经填完前 (i) 行,有 (j) 列已经填有 (1) 的方案。转移的话可以枚举当前有多少个位置是刚刚添加 (1),其余的位置随意:

    [dp_{i, j} = sumlimits_{k = 0} ^ j dbinom{n - j + k}{k} imes m ^ {j - k} imes (m - 1) ^ {n - j} dp_{i - 1, j - k} ]

    但是这个转移在 (k = 0) 时是失效的,因此 (k = 0) 的情况我们需要特殊转移,有 (dp_{i, j} = (m ^ j imes (m - 1) ^ {n - j} - (m - 1) ^ n) dp_{i - 1, j})(j) 个位置中至少有一个 (1) 的方案,容斥一下即可。

    #include<bits/stdc++.h>
    using namespace std;
    #define rep(i, l, r) for(int i = l; i <= r; ++i)
    const int N = 250 + 5;
    const int Mod = 1000000000 + 7;
    int n, m, P1[N], P2[N], C[N][N], dp[N][N];
    int read(){
        char c; int x = 0, f = 1;
        c = getchar();
        while(c > '9' || c < '0'){ if(c == '-') f = -1; c = getchar();}
        while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
        return x * f;
    }
    int Inc(int a, int b){
        return (a += b) >= Mod ? a - Mod : a;
    }
    int Dec(int a, int b){
        return (a -= b) < 0 ? a + Mod : a;
    }
    int Mul(int a, int b){
        return 1ll * a * b % Mod;
    }
    int Qpow(int a, int b){
        int ans = 1;
        while(b){
            if(b & 1) ans = Mul(ans, a);
            a = Mul(a, a), b >>= 1;
        }
        return ans;
    }
    int main(){
        n = read(), m = read();
        P1[0] = P2[0] = 1;
        rep(i, 1, n) P1[i] = Mul(P1[i - 1], m), P2[i] = Mul(P2[i - 1], m - 1);
        rep(i, 0, n) C[i][0] = 1;
        rep(i, 1, n) rep(j, 1, i) C[i][j] = Inc(C[i - 1][j], C[i - 1][j - 1]);
        dp[0][0] = 1;
        rep(i, 1, n) rep(j, 0, n){
            rep(k, 0, j - 1) dp[i][j] = Inc(dp[i][j], Mul(dp[i - 1][k], Mul(C[n - k][j - k], Mul(P1[k], P2[n - j]))));
            dp[i][j] = Inc(dp[i][j], Mul(dp[i - 1][j], Mul(Dec(P1[j], P2[j]), P2[n - j])));
        }
        printf("%d", dp[n][n]);
        return 0;
    }
    

    上面这个 (dp) 能做到 (O(n ^ 3)),虽然能通过本题,但有没有什么更好的办法呢?可以发现,本题中唯一难限制的点就是每一行每一列都必须存在至少一个 (1),那么我们可以考虑一下二项式反演钦定某些行某些列有 (1) 其他行随意的方案,发现这不好算,因为行和列之间会有重复部分,这样放 (1) 的位置会影响每行每列的情况。那么,我们再反过来思考,能否钦定一些行一些列没有放 (1) 呢?可以发现这是非常好算的,令 (f_{i, j}) 表示钦定有 (i) 行没有填 (1),有 (j) 列没有填 (1) 其他位置随意的方案。那么有:

    [f_{i, j} = dbinom{n}{i} imes dbinom{n}{j} imes (m - 1) ^ {n(i + j) - i imes j} imes m ^ {n ^ 2 - n(i + j) + i imes j} ]

    那么令 (g_{i, j}) 表示恰好有 (i)(j) 列没有填 (1) 的方案,那么就有 (f_{x, y} = sumlimits_{i = x} ^ n sumlimits_{j = y} ^ n dbinom{i}{x} dbinom{j}{y} g_{i, j}),运用高维二项式反演有 (g_{x, y} = sumlimits_{i = x} ^ n sumlimits_{j = y} ^ n (-1) ^ {i + j - x - y} dbinom{i}{x} dbinom{j}{y} f_{i, j}) 那么就会有:

    [g_{0, 0} = sumlimits_{i = 0} ^ n sumlimits_{j = 0} ^ n (-1) ^ {i + j} dbinom{n}{i} imes dbinom{n}{j} imes (m - 1) ^ {n(i + j) - i imes j} imes m ^ {n ^ 2 - n(i + j) + i imes j} ]

    从这之后往下推,我尝试过很多方法,主要的难点在于如何将 ((m - 1) ^ {n(i + j) - i imes j} imes m ^ {n ^ 2 - n(i + j) + i imes j}) 搞在一起去。熟悉因式分解的话可以发现 (n ^ 2 - n(i + j) + i imes j = (n - i)(n - j))(n(i + j) - i imes j = (n - j)i + nj) 其中有相同的因式,于是我们可以考虑将 ((m - 1) ^ {(n - j)i + nj}) 拆开:

    [g_{0, 0} = sumlimits_{i = 0} ^ n sumlimits_{j = 0} ^ n (-1) ^ {i + j} dbinom{n}{i} imes dbinom{n}{j} imes (m - 1) ^ {(n - j)i} imes m ^ {(n - i)(n - j)} imes (m - 1) ^ {nj} ]

    将包含因式相同的两个式子搞在一起:

    [g_{0, 0} = sumlimits_{i = 0} ^ n sumlimits_{j = 0} ^ n (-1) ^ {i + j} dbinom{n}{i} imes dbinom{n}{j} imes ((m - 1) ^ i imes m ^ {(n - i)}) ^ {n - j} imes (m - 1) ^ {nj} ]

    然后我们惊奇地发现 (n - j + j = n) 配合前面的 (dbinom{n}{j}) 是一个二项式定理的形式,于是可以有如下推导:

    [egin{aligned} g_{0, 0} &= sumlimits_{i = 0} ^ n (-1) ^ i dbinom{n}{i} sumlimits_{j = 0} ^ n (-1) ^ j imes dbinom{n}{j} imes ((m - 1) ^ i imes m ^ {(n - i)}) ^ {n - j} imes ((m - 1) ^ n) ^ j\ &= sumlimits_{i = 0} ^ n (-1) ^ i dbinom{n}{i} ((m - 1) ^ i imes m ^ {(n - i)} - (m - 1) ^ n) ^ n end{aligned} ]

    于是只需预处出 (P1_i = m ^ i, P2_i = (m - 1) ^ i) 即可做到 (O(n log n))

    #include<bits/stdc++.h>
    using namespace std;
    #define rep(i, l, r) for(int i = l; i <= r; ++i)
    const int N = 250 + 5;
    const int Mod = 1000000000 + 7;
    int n, m, ans, P1[N], P2[N], fac[N], inv[N];
    int read(){
        char c; int x = 0, f = 1;
        c = getchar();
        while(c > '9' || c < '0'){ if(c == '-') f = -1; c = getchar();}
        while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
        return x * f;
    }
    int Inc(int a, int b){
        return (a += b) >= Mod ? a - Mod : a;
    }
    int Dec(int a, int b){
        return (a -= b) < 0 ? a + Mod : a;
    }
    int Mul(int a, int b){
        return 1ll * a * b % Mod;
    }
    int Qpow(int a, int b){
        int ans = 1;
        while(b){
            if(b & 1) ans = Mul(ans, a);
            a = Mul(a, a), b >>= 1;
        }
        return ans;
    }
    int C(int n, int m){
        if(m > n) return 0;
        return Mul(fac[n], Mul(inv[m], inv[n - m]));
    }
    int main(){
        n = read(), m = read();
        fac[0] = inv[0] = P1[0] = P2[0] = 1;
        rep(i, 1, n){
            fac[i] = Mul(fac[i - 1], i), inv[i] = Qpow(fac[i], Mod - 2);
            P1[i] = Mul(P1[i - 1], m), P2[i] = Mul(P2[i - 1], m - 1);
        } 
        rep(i, 0, n){
            if(i & 1) ans = Dec(ans, Mul(C(n, i), Qpow(Dec(Mul(P1[n - i], P2[i]), P2[n]), n)));
            else ans = Inc(ans, Mul(C(n, i), Qpow(Dec(Mul(P1[n - i], P2[i]), P2[n]), n)));
        }
        printf("%d", ans);
        return 0;
    }
    

    双倍经验 [JSOI2015]染色问题

    GO!
  • 相关阅读:
    进程池和线程池
    TCP并发、GIL、锁
    进程间通信
    装饰器与反射
    装饰器大全
    面向对象三大特征: 封装 继承 多态
    面向对象 魔术方法
    魔术方法
    ubuntu 中导 tarfile,win 不亲切
    os VS shutil
  • 原文地址:https://www.cnblogs.com/Go7338395/p/13595606.html
Copyright © 2011-2022 走看看