这道 Cu 的 AGC F 竟然被我自己想出来了!!!(((
首先考虑什么样的序列会被统计入答案。稍微手玩几组数据即可发现,一个颜色序列 \(c_1,c_2,\cdots,c_{nk}\) 满足条件当前仅当对于从左往右数第 \(i\) 个 \(0\) 号颜色的位置 \(p_i\),\([1,p_i-1]\) 中非零颜色的种类 \(<i\)。简单证明一下,必要性:如果 \(\exist i\in[1,n]\) 满足 \([1,p_i-1]\) 中颜色种类 \(\ge i\),那么 \([1,p_i-1]\) 中至少出现了 \(i\) 种颜色,也就至少应当有 \(i\) 个 \(0\) 号颜色的球。但事实上 \([1,p_i-1]\) 中只有 \(i-1\) 个 \(0\) 号颜色的球,矛盾。充分性:我们假设第 \(i\) 种颜色第二次出现的位置(也就是第一次出现的位置被替换为 \(0\) 后,剩余部分第一次出现的位置)为 \(q_i\),我们将 \(q_i\) 从小到大排个序,由假设第 \(i\) 小的 \(q_i\) 必定 \(\le p_i\),因此我们只需将第 \(i\) 个黑球替换为第 \(i\) 小的 \(q_i\) 对应的颜色即可还原出原序列。
接下来考虑发现这个性质之后怎样计算答案,我们考虑一个填补空隙的思想,设 \(dp_{i,j}\) 表示填好了 \(i\) 个黑球的位置以及前 \(j\) 种颜色的剩余 \(k-1\) 个球的位置的方案数,考虑转移,由以下两种情况:
- 我们新放了一个黑球,为了避免重复,我们强制要求该黑球必须放在从左往右数的第一个空隙处,方案唯一,即 \(dp_{i+1,j}\leftarrow dp_{i,j}\)
- 我们新添了一种颜色,那么我们需先从剩余 \(n-j\) 种颜色中选择一种颜色,再将这种颜色的剩余 \(k-1\) 个球任意放入剩余 \(nk-j(k-1)-i\) 个空隙,方案数为 \(\dbinom{nk-j(k-1)-i}{k-1}\times(n-j)\)。可真的是这样吗?不难发现这样会重复计算,举个例子,\(n=2,k=2\),现在已经填好了两个 \(0\) 的位置,即 \(0\ 0\ -\ -\)(其中 \(-\) 表示空隙),那么我们在 \(dp\) 转移的过程中会出现先选择一个 \(1\),填入 \(3\) 位置,再选择一个 \(2\),填入 \(4\) 位置,即 \(0\ 0\ -\ -\to 0\ 0\ 1\ -\to 0\ 0\ 1\ 2\),也有可能出现先选择一个 \(2\),填入 \(4\) 位置,再选择一个 \(1\),填入 \(3\) 位置,\(0\ 0\ -\ -\to 0\ 0\ -\ 2\to 0\ 0\ 1\ 2\),不难发现这两种情况的结局是一样的,都是 \(0\ 0\ 1\ 2\),不过却因为钦定填颜色的顺序不同而被我们算了两次。怎样解决这个问题呢?这里有一个避免重复的技巧,我们还是将 \(k-1\) 个球塞入 \(nk-j(k-1)-i\) 个空隙,不过我们强制要求有一个球必须放入从左往右数的第一个空隙中,方案数就变为了 \(\dbinom{nk-j(k-1)-i-1}{k-2}\times(n-j)\),不难发现在这种情况下,我们塞球的顺序永远是先塞第一次出现位置最靠前的颜色,也就不会出现上面的情况了。\(dp\) 转移方程式为 \(dp_{i,j+1}\leftarrow dp_{i,j}\times\dbinom{nk-j(k-1)-i-1}{k-2}\times(n-j)\)。
到这里你可能还有个小问题,就是上面“从左往右数第 \(i\) 个 \(0\) 号颜色的位置 \(p_i\),\([1,p_i-1]\) 中非零颜色的种类 \(<i\)”怎样在 DP 方程中体现出来。其实很简单,我们在求 DP 值的时候只保留 \(j\le i\) 的 \(dp_{i,j}\) 即可,显然这样转移总是合法的。
时间复杂度 \(\mathcal O(nk)\)。
最后,文件名打错,爆零两行泪,这题是某场模拟赛的题,现场想到正解了,却因为文件名打错而爆零了/kk
const int MAXN=2e3;
const int MOD=1e9+7;
int n,k,dp[MAXN+5][MAXN+5];
int fac[MAXN*MAXN+5],ifac[MAXN*MAXN+5];
void init_fac(int n){
fac[0]=ifac[0]=ifac[1]=1;
for(int i=2;i<=n;i++) ifac[i]=1ll*ifac[MOD%i]*(MOD-MOD/i)%MOD;
for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%MOD,ifac[i]=1ll*ifac[i]*ifac[i-1]%MOD;
}
int binom(int x,int y){return 1ll*fac[x]*ifac[x-y]%MOD*ifac[y]%MOD;}
int main(){
// freopen("colorful.in","r",stdin);freopen("colorful.out","w",stdout);
scanf("%d%d",&n,&k);if(!(k^1)) return printf("1\n"),0;
dp[0][0]=1;init_fac(n*k);
for(int i=1;i<=n;i++) for(int j=0;j<=i;j++){
dp[i][j]=(dp[i-1][j]+1ll*dp[i][j-1]*binom(n*k-(j-1)*(k-1)-i-1,k-2)%MOD*(n-j+1)%MOD)%MOD;
} printf("%d\n",dp[n][n]);
return 0;
}