CF140E New Year Garland (计数问题)
用$m$种颜色的彩球装点$n$层的圣诞树。圣诞树的第$i$层恰由$a_{i}$个彩球串成一行,且同一层内的相邻彩球颜色不同,同时相邻两层所使用彩球的颜色集合不 同。求有多少种装点方案,答案对$p$取模。
好神的计数问题,zwz Orz
题解思路来自黄学长hzwer的博客
先只考虑在一行内的彩球的方案数
定义$g[i][j]$表示一共有$i$个球串成一行,一共用了$j$种颜色的方案数
因为所有颜色都是等价的,我们可以利用最小表示法来简化计数,比如让颜色编号为$x+1$的球第一次出现的位置,在颜色编号为$x$的球之前。实际的方案数是$g[i][j]\cdot j!$
这样递推关系就简单多了
加入一个新颜色的球,$g[i][j]+=g[i-1][j-1]$
加入一个旧颜色的球,颜色不能和第$i-1$个球相同,$g[i][j]+=(j-1)g[i-1][j]$
$g[i][j]=g[i-1][j-1]+(j-1)g[i-1][j]$
在考虑行行之间的影响
定义$f[i][j]$表示前$i$行,其中第$i$行选了$j$种颜色的方案数
如果没有相邻两行集合不同这种限制
$f[i][j]=C_{m}^{j}\cdot g[a_{i}][j]\cdot \sum f[i-1][k]$
如果加上限制,
$f[i][j]=C_{m}^{j}\cdot g[a_{i}][j]\cdot j!\sum f[i-1][k]-g[a_{i}][j]\cdot j!\cdot f[i-1][j]$
$=A_{m}^{j}\cdot g[a_{i}][j]\sum f[i-1][k]-g[a_{i}][j]\cdot j!\cdot f[i-1][j]$
利用前缀和优化可以$O(1)$转移
$f[i][j]$的状态数也仅仅是$O(\sum a_{i})$,用滚动数组记录
$A_{m}^{j}$和$j!$可以通过预处理得到
总结:由于本题的模数是非质数,用组合数计数会让问题变得复杂,且时间复杂度很难保证。如果本题保证模数为质数,可能会有很多其他做法,比如组合数+容斥等等,但应该都没有官方题解的思路简洁。出题者似乎引导我们走向突破口——消去组合数,突破口在于通过化简,把组合数化成排列数和阶乘,排列数和阶乘即使在模数为非质数的情况下,也能在$O(n)$时间预处理
1 #include <cmath> 2 #include <cstdio> 3 #include <cstring> 4 #include <algorithm> 5 #define N1 5010 6 #define M1 1000010 7 #define dd double 8 #define ll long long 9 using namespace std; 10 11 int gint() 12 { 13 int ret=0,fh=1;char c=getchar(); 14 while(c<'0'||c>'9'){if(c=='-')fh=-1;c=getchar();} 15 while(c>='0'&&c<='9'){ret=ret*10+c-'0';c=getchar();} 16 return ret*fh; 17 } 18 int n,m,mx,P; 19 20 int f[2][N1],g[N1][N1],am[N1],mul[N1],a[M1]; 21 22 int main() 23 { 24 int i,j,x; 25 scanf("%d%d%d",&n,&m,&x); 26 for(i=1;i<=n;i++) scanf("%d",&a[i]), mx=max(mx,a[i]); 27 const int p=x; 28 g[0][0]=g[1][1]=1; 29 for(i=2;i<=mx;i++) for(j=1;j<=i;j++) g[i][j]=(1ll*(j-1)*g[i-1][j]%p+g[i-1][j-1])%p; 30 mul[0]=mul[1]=1; am[0]=1; 31 for(i=1;i<=min(m,mx);i++) am[i]=1ll*am[i-1]*(m-i+1)%p; 32 for(i=2;i<=mx;i++) mul[i]=1ll*mul[i-1]*i%p; 33 int now=1,pst=0,snow=0,spst=1; 34 f[pst][0]=1; 35 for(i=1;i<=n;i++) 36 { 37 snow=0; 38 for(j=1;j<=min(m,a[i]);j++) 39 f[now][j]=(1ll*am[j]*g[a[i]][j]%p*spst%p-1ll*f[pst][j]*mul[j]%p*g[a[i]][j]%p+p)%p, (snow+=f[now][j])%=p; 40 memset(f[pst],0,(min(m,a[i-1])+1)<<2); 41 swap(now,pst); swap(snow,spst); 42 } 43 printf("%d\n",spst); 44 return 0; 45 }