【AtCoder】AGC022 F - Leftmost Ball 计数DP

【题目】F - Leftmost Ball

【题意】给定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]);
}
View Code

 

posted @ 2018-04-17 14:23  ONION_CYC  阅读(417)  评论(0编辑  收藏  举报