Counting 苦手本来都准备白兰了,但祁神发现了关键的性质然后就发现可做了
稍作观察我们就可以发现对于一个最终合法的序列,其任意一个前缀中白球的数量都必须大于等于这段前缀的颜色数
直接对长度为 \(n\times k\) 的序列 DP 复杂度显然不能接受,不过我们发现我们只关心每种颜色出现的第一个球的位置,对于剩下的 \(k-2\) 个球其实往后随便怎么放都不影响序列的合法性
设状态 \(f_{i,j}\) 表示已经放了 \(i\) 个白球,之前共出现了 \(j\) 种颜色,且这 \(j\) 种颜色的球已经全部放完的方案数,任何时刻都需要保证 \(i\ge j\)
转移分两类,一种是放一个白球,这种情况就是 \(f_{i+1,j}\leftarrow f_{i,j}\),否则考虑放一个之前没出现的颜色的球
首先选颜色有 \(n-j\) 种方案,这种颜色剩下的 \(k-2\) 个球可以放在后面剩下的 \(n\times k-i-1-j\times (k-1)\) 个位置,用组合数算一下即可
总复杂度 \(O(nk)\),注意特判 \(k=1\) 的情形
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=2005,mod=1e9+7;
int n,k,f[N][N],fact[N*N],ifac[N*N];
inline void inc(int& x,CI y)
{
if ((x+=y)>=mod) x-=mod;
}
inline int quick_pow(int x,int p=mod-2,int mul=1)
{
for (;p;p>>=1,x=1LL*x*x%mod) if (p&1) mul=1LL*mul*x%mod; return mul;
}
inline void init(CI n)
{
RI i; for (fact[0]=i=1;i<=n;++i) fact[i]=1LL*fact[i-1]*i%mod;
for (ifac[n]=quick_pow(fact[n]),i=n-1;i>=0;--i) ifac[i]=1LL*ifac[i+1]*(i+1)%mod;
}
inline int C(CI n,CI m)
{
if (n<0||m<0||n<m) return 0;
return 1LL*fact[n]*ifac[m]%mod*ifac[n-m]%mod;
}
int main()
{
RI i,j; scanf("%d%d",&n,&k);
if (k==1) return puts("1"),0;
init(n*k); f[0][0]=1;
for (i=0;i<=n;++i) for (j=0;j<=i;++j)
{
if (!f[i][j]) continue;
//printf("f[%d][%d] = %d\n",i,j,f[i][j]);
inc(f[i+1][j],f[i][j]);
inc(f[i][j+1],1LL*f[i][j]*(n-j)%mod*C(n*k-i-1-j*(k-1),k-2)%mod);
}
return printf("%d",f[n][n]),0;
}
标签:CI,Ball,ifac,int,AGC02F,inline,mul,Leftmost,mod
From: https://www.cnblogs.com/cjjsb/p/18321617