因为要保证每行都必须填一个 (1),于是我们需要逐层考虑并在这层填若干个 (1),为了能描述当前的状态,我们可以令 (dp_{i, j}) 表示当前已经填完前 (i) 行,有 (j) 列已经填有 (1) 的方案。转移的话可以枚举当前有多少个位置是刚刚添加 (1),其余的位置随意:
但是这个转移在 (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) 其他位置随意的方案。那么有:
那么令 (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}) 那么就会有:
从这之后往下推,我尝试过很多方法,主要的难点在于如何将 ((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}) 拆开:
将包含因式相同的两个式子搞在一起:
然后我们惊奇地发现 (n - j + j = n) 配合前面的 (dbinom{n}{j}) 是一个二项式定理的形式,于是可以有如下推导:
于是只需预处出 (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]染色问题