Atcoder题解:Agc002_f

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

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

然后设 dpi,j 为目前安排了 i0 球和 j 种颜色,然后就枚举下一个安排的是不是 0 球即可。如果是,直接往 i+1 转移。如果不是,首先要 j+1i,然后钦定下一个是,对于剩下的 k2 个球,可以在剩下的 nkj(k1)1 个位置里随便选。方案数是 (nkj(k1)1k2)

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


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
posted @   jucason_xu  阅读(13)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
点击右上角即可分享
微信分享提示