Atcoder题解:Agc002_f

发布时间 2023-04-16 19:07:44作者: jucason_xu

我们可以把这个理解成一种类似卡塔兰数的形式,我们发现,被安排的 \(0\) 球总数 \(i\) 和已经出现的颜色种数 \(j\) 在任意时刻都必须满足 \(i\ge j\)

然后就可以 \(dp\) 了,我们每次钦定下一个转移的球是某种颜色。如果下一个转移的球不是 \(0\),那么我们就一次性把后面所有这种颜色都安排好,然后把这些位置去掉。在所有这些位置中,钦定接下来的第一个空位置是这种颜色,从而得到颜色之间强制性的顺序,以消除算重的可能性。

然后设 \(dp_{i,j}\) 为目前安排了 \(i\)\(0\) 球和 \(j\) 种颜色,然后就枚举下一个安排的是不是 \(0\) 球即可。如果是,直接往 \(i+1\) 转移。如果不是,首先要 \(j+1\le i\),然后钦定下一个是,对于剩下的 \(k-2\) 个球,可以在剩下的 \(nk-j(k-1)-1\) 个位置里随便选。方案数是 \(\begin{pmatrix}nk-j(k-1)-1 \\k-2\end{pmatrix}\)

预处理阶乘和阶乘的逆元(如果暴力逆元可能 \(O(nk\log P)\) 会被卡,所以可能需要线性逆元),就得到了一个 \(O(n^2)\) 的做法。


const ll P=1000000007;
ll fac[4000005],ifac[4000005],n,k;
inline ll fpow(ll a,ll p){
	if(!p)return 1;
	ll res=fpow(a,p>>1);
	if(p&1)return res*res%P*a%P;
	return res*res%P;
}
inline ll inv(ll a){
	return fpow(a,P-2);
}
inline void init(){
	fac[0]=1;ifac[0]=1;
	for(int i=1;i<=4000000;i++)fac[i]=fac[i-1]*i%P;
	for(int i=1;i<=4000000;i++)ifac[i]=ifac[i-1]*fac[i]%P;
	ifac[4000000]=inv(ifac[4000000]);
	for(int i=4000000;i>=1;i--){
		int tmp=ifac[i];
		ifac[i]=ifac[i]*ifac[i-1]%P;
		ifac[i-1]=tmp*fac[i]%P;
	}
}
inline ll C(int n,int m){
	return fac[n]*ifac[m]%P*ifac[n-m]%P;
}
ll dp[2005][2005];
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin>>n>>k;
	init();
	if(k==1){
		cout<<1<<endl;
		return 0;
	}
	dp[0][0]=1;
	for(int i=0;i<=n;i++){
		for(int j=0;j<=i;j++){
			if(i!=n){
				dp[i+1][j]=(dp[i+1][j]+dp[i][j])%P;
			}//n-(k-1)*j-i
			if(j!=i){
				dp[i][j+1]=(dp[i][j+1]+dp[i][j]*C(n*k-(k-1)*j-i-1,k-2)%P)%P;
			}
		}
	}cout<<dp[n][n]*fac[n]%P<<endl;
	return 0;
}
//Crayan_r