【AtCoder】AGC022 F - Leftmost Ball 计数DP
【题意】给定n种颜色的球各k个,每次以任意顺序排列所有球并将每种颜色最左端的球染成颜色0,求有多少种不同的颜色排列。n,k<=2000。
【算法】计数DP
【题解】只看黑体字部分即可。
自己考虑了几种计数方案,都不能实现。一种从左到右,但要记录每种球剩余多少,不可行。一种从右到左枚举白球包含区间填充,但因为只看白球,每种颜色没有关键球,会有重复统计的问题。
计数的关键在于白球的原色不重要以及每种颜色关注最左端的球(这里不含变成白球的球)。
本题既然nk能分开,就没必要整个序列推进,只抽象地枚举白球数和颜色数就可以了,这是因为本题的条件很优美,不需要考虑位置的因素。
设$f[i][j]$表示前i个白球,仅包含j种颜色的最大前缀的排列数。这里只要保证$j \leq i$就可以满足颜色数不多于白球数。
转移可以增加一个白球,或增加一种颜色。增加颜色时固定最左端球的位置,然后在前面球剩余的位置任意填充。(这里既不考虑填充前面剩余位置的问题,因为紧凑。也不考虑前面每种球的剩余数量,因为每种填充方案的剩余位置数都是确定的。计数问题常常考虑每一种前面的方案共有的性质然后抽象出来转移,即对于前面每一个方案如何如何后就变成现在的方案)
$$f[i][j]=f[i-1][j]+\binom{n-i+(n-j+1)*(k-1)-1}{k-2}*(n-j+1)*f[i][j-1]$$
后面的(n-j+1)可以改为最后乘上n!。
复杂度O(n^2)。
代码来自:zhanglexing
#include<cstdio> #define ll long long const int N=2005,M=4e6+5,mo=1e9+7; int n,m,nm,i,j,f[N]; int fact[M],inv[M],fv[M]; void init(int n){ fact[0]=fact[1]=inv[1]=fv[0]=fv[1]=1; for (i=2;i<=n;i++){ fact[i]=(ll)fact[i-1]*i%mo; inv[i]=(ll)inv[mo%i]*(mo-mo/i)%mo; fv[i]=(ll)fv[i-1]*inv[i]%mo; } } int C(int n,int m){return (ll)fact[n]*fv[m]%mo*fv[n-m]%mo;} int main(){ scanf("%d%d",&n,&m);m--; if (!m){puts("1");return 0;} nm=n*m+n;init(nm);f[0]=1; for (i=1;i<=n;i++) for (j=1;j<=i;j++) f[j]=(f[j]+(ll)(n-j+1)* C(nm-i-(j-1)*m-1,m-1)%mo*f[j-1])%mo; printf("%d",f[n]); }