非常妙的计数 dp 题,有利于我们训练思维。
Description
Solution
首先,本题显然是一道计数 dp \text{dp} dp 题。在构造 dp \text{dp} dp 之前,我们不妨先考虑什么样的盘面 a a a 是合法的。
不难得到下面的判定算法:考虑从左到右扫描 a a a,维护前缀中 0 0 0 的数量及非 0 0 0 颜色的种数,那么 a a a 合法当且仅当前者始终不小于后者。
考虑将上述判定流程内化到 dp \text{dp} dp 状态中。令 f i , j f_{i,j} fi,j 表示,目前钦定了前 i i i 个 0 0 0,在钦定过程中也钦定了 j j j 种非 0 0 0 的颜色,此时的方案数。转移的时候,分成两类考虑:
- 填上 0 0 0,则 f i , j → f i + 1 , j f_{i,j} \to f_{i+1,j} fi,j→fi+1,j
- 扩充一种新颜色。不难发现有 n − j n-j n−j 种颜色备选,且选定了颜色过后,要在空余的 n k − i − j ( k − 1 ) − 1 nk-i-j(k-1)-1 nk−i−j(k−1)−1 个位置中选择 k − 2 k-2 k−2 个位置放上该颜色。则 ( n − j ) ( n k − i − j ( k − 1 ) − 1 k − 2 ) f i , j → f i , j + 1 (n-j) {nk-i-j(k-1)-1 \choose k-2} f_{i,j} \to f_{i,j+1} (n−j)(k−2nk−i−j(k−1)−1)fi,j→fi,j+1。
边界:
f
0
,
0
=
1
f_{0,0}=1
f0,0=1
答案:
f
n
,
n
f_{n,n}
fn,n
特判 k = 1 k=1 k=1 即可。时间复杂度 O ( n 2 ) O(n^2) O(n2)。
Summary
想到了判定方法,但为什么我没有设计出 dp \text{dp} dp 呢?这不是很自然吗?可能是……躺床上枕头有点翘导致脖子那里血脉不是特别通畅导致的?
Code
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=2005,maxp=4000005,mod=1e9+7;
int n,k;
int jc[maxp],inv[maxp],f[maxn][maxn];
void chksum(int x,int &y){y=(y+x)%mod;}
int quick_power(int x,int y){
int res=1;
for (;y;y=y>>1,x=(x*x)%mod){
if (y&1) res=(res*x)%mod;
}
return res;
}
void init(int n){
jc[0]=1;
for (int i=1;i<=n;i++) jc[i]=(jc[i-1]*i)%mod;
inv[n]=quick_power(jc[n],mod-2);
for (int i=n-1;i>=0;i--) inv[i]=(inv[i+1]*(i+1))%mod;
}
int C(int x,int y){
if (x<0||y<0||x<y) return 0;
return ((jc[x]*inv[y])%mod*inv[x-y])%mod;
}
signed main(){
cin>>n>>k,init(maxp-5),f[0][0]=1;
if (k==1) return puts("1"),0;
for (int i=0;i<=n;i++){
for (int j=0;j<=i;j++){
if (i<n) chksum(f[i][j],f[i+1][j]);
if (j<i) chksum(f[i][j]*(n-j)%mod*C(n*k-i-j*(k-1)-1,k-2),f[i][j+1]);
}
}
cout<<f[n][n]<<endl;
return 0;
}