CF1228E Another Filling the Grid

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

\[dp_{i, j} = \sum\limits_{k = 0} ^ j \dbinom{n - j + k}{k} \times m ^ {j - k} \times (m - 1) ^ {n - j} dp_{i - 1, j - k} \]

但是这个转移在 \(k = 0\) 时是失效的,因此 \(k = 0\) 的情况我们需要特殊转移,有 \(dp_{i, j} = (m ^ j \times (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} \times \dbinom{n}{j} \times (m - 1) ^ {n(i + j) - i \times j} \times m ^ {n ^ 2 - n(i + j) + i \times j} \]

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

\[g_{0, 0} = \sum\limits_{i = 0} ^ n \sum\limits_{j = 0} ^ n (-1) ^ {i + j} \dbinom{n}{i} \times \dbinom{n}{j} \times (m - 1) ^ {n(i + j) - i \times j} \times m ^ {n ^ 2 - n(i + j) + i \times j} \]

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

\[g_{0, 0} = \sum\limits_{i = 0} ^ n \sum\limits_{j = 0} ^ n (-1) ^ {i + j} \dbinom{n}{i} \times \dbinom{n}{j} \times (m - 1) ^ {(n - j)i} \times m ^ {(n - i)(n - j)} \times (m - 1) ^ {nj} \]

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

\[g_{0, 0} = \sum\limits_{i = 0} ^ n \sum\limits_{j = 0} ^ n (-1) ^ {i + j} \dbinom{n}{i} \times \dbinom{n}{j} \times ((m - 1) ^ i \times m ^ {(n - i)}) ^ {n - j} \times (m - 1) ^ {nj} \]

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

\[\begin{aligned} g_{0, 0} &= \sum\limits_{i = 0} ^ n (-1) ^ i \dbinom{n}{i} \sum\limits_{j = 0} ^ n (-1) ^ j \times \dbinom{n}{j} \times ((m - 1) ^ i \times m ^ {(n - i)}) ^ {n - j} \times ((m - 1) ^ n) ^ j\\ &= \sum\limits_{i = 0} ^ n (-1) ^ i \dbinom{n}{i} ((m - 1) ^ i \times 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]染色问题

posted @ 2020-09-01 13:15  Achtoria  阅读(120)  评论(0编辑  收藏  举报