洛谷 AT2000 Leftmost Ball
数据范围告诉我们需要一个\(O(n^2)\)级别的做法
首先朴素DP
Step 1
\(dp[i][...]\)表示前\(i\)个球和每种颜色的球放了多少个
显然我们必须要记录每种颜色的球放了多少才能完美解决,因此枚举放到第几个球是不行的
Step 2
\(dp[..]\)表示每种颜色的球放了多少个
因为白球是同种颜色球中最靠左的那个,为了方便和去重,我们考虑每个白球都是放在从左往右第一个空位,其他的球随意找空
因为我们假定了每一种球是不同的,最后要除以\((k-1)\)个球的排列
Step 3
\(dp[i][j]\)表示当前放了\(i\)个白球,\(j\)种颜色的球
因为白球一定是同种颜色的球最前面的那个
我们把白球和其他的球分开来看
对于不同颜色的球,我们发现放法就是从当前剩余的空位中找出来\((k-1)\)个空的组合数
因此可以考虑每次放一种颜色的球
每次对序列操作,要么放一个白球,要么把一种颜色的\(k-1\)个球全部放入
为了避免重复,我们每次只考虑从左往右第一个空位置上放什么球,
\[dp[i][j]=dp[i-1][j]+dp[i][j-1]*(n-j+1)*C_{n*k-(j-1)*(k-1)-i-1}^{k-2}
\]
解释一下:
\(dp[i-1][j]\)表示当前放了一个白球的转移
\(dp[i][j-1]*(n-j+1)*C_{n*k-(j-1)*(k-1)-i-1}^{k-2}\)表示当前放了一种颜色的球的转移
其中\(n-j+1\)表示从剩下的颜色中选出一种颜色,放一个球在最前面的空位
然后在\(n*k-(j-1)*(k-1)-i-1\)个空位中选出\(k-2\)个给剩下的球放
注意\(dp[i][j]\)要保证\(i>=j\)时才有意义
[Code]
#include<bits/stdc++.h>
using namespace std;
int read(){
int x=0; char c=getchar(); int flag=1;
while(!isdigit(c)) { if(c=='-') flag=-1; c=getchar(); }
while(isdigit(c)) { x=((x+(x<<2))<<1)+(c^48); c=getchar(); }
return x*flag;
}
const int mod=1e9+7;
int n,k;
int dp[2005][2005];//i个白球,j种颜色
int fac[4000005],Inv[4000005];
int C(int n,int m){
if(m<0||n<m) return 0;
return 1ll*(1ll*fac[n]*Inv[m]%mod)*Inv[n-m]%mod;
}
void exgcd(int a,int b,int &x,int &y){
if(b==0) { x=1; y=0; return ; }
exgcd(b,a%b,y,x); y-=a/b*x;
}
int inv(int a){
int x=0,y=0;
exgcd(a,mod,x,y);
return (x%mod+mod)%mod;
}
signed main(){
fac[0]=1;
for(int i=1;i<=4000000;i++) fac[i]=1ll*fac[i-1]*i%mod;
Inv[4000000]=inv(fac[4000000]);
for(int i=3999999;i>=0;i--) Inv[i]=1ll*Inv[i+1]*(i+1)%mod;
n=read(),k=read();
if(k==1) { puts("1"); return 0; }
dp[0][0]=1;
for(int i=1;i<=n;i++){
dp[i][0]=1;
for(int j=1;j<=i;j++){
dp[i][j]=(dp[i-1][j]+1ll*(1ll*dp[i][j-1]*C(n*k-(j-1)*(k-1)-i-1,k-2))%mod*(n-j+1)%mod)%mod;
}
}
printf("%d\n",dp[n][n]);
return 0;
}