当然可以打组合数+CRT什么的,但是其实不必那么麻烦。
先讲那个思路,再转化过来吧。
首先可以发现的一个问题:所有颜色之间是没有区别的,所以我们其实并不在意到底是哪几种,我们只需要知道有几种就可以了。
逐层递推:设dp[i][j]表示到了第i层,这层用了某j种颜色的总方案数。
注意这里为了方便去重,所以dp的含义里是已经确定了j种颜色,所以不要乘上 $ C_m^j $
那么再设s[i]是填到第i曾为止的总方案数那么s[i]=Σdp[i][j]*C[m][j]
还需要预处理一下g数组,g[i][j]表示用恰好i种颜色填满j个格子的方案数。而这j中颜色也是未钦定的所以不带组合数。
那么dp的转移式也就有了,dp[i][j]=(s[i-1]-dp[i-1][j])*g[j][a[i]];
只从上一层转移而来所以可以滚动。
f的预处理也很简单,转移就是从长度短1的状态中选择一种颜色填上。
g[i][j]=g[i-1][j]*(j-1)+g[i-1][j-1]*j;即如果不新增颜色的话那么你能从已有的j种里除上一位刚填的那一种外其他的颜色里选一种,即j-1。
如果你新开了一种颜色,那么就要决定这新开的一种颜色是现有的j种里的哪一种,即乘j。
所以整体思路还是比较简单的。并没有调出我的CRT所以不粘这个代码。
然后想一想简化:当模数不是质数时除法会出问题而加减乘都没事。然而整个代码里只有组合数那一步需要除法。
那么怎么搞掉组合数呢?
需要一些脑洞,我们新开一个数组f。f表示的和g数组差不多,但是是已经钦定了具体是哪j种颜色。
所以g的转移就是f[i][j]=f[i-1][j-1]*(m+1-j)+f[i-1][j]*(j-1);
如果不新增颜色的话那么和f是一样的转移,但是如果是新增了颜色,因为颜色需要确定,所以我们需要明确知道新加入的是哪种颜色。
那就是在还没有被选过的颜色里选一个,一共m种已经用了j-1种,那么剩余的选择就是m-j+1。
那么dp的含义也随之变化,dp亦表示已钦定后的值,那么就相当与乘上那个组合数了。
再考虑dp转移。dp[i][j]=s[i-1]*f[j][a[i]]-dp[i-1][j]*g[j][a[i]];含义的话也类似。
在总方案里我们是已钦定的所以本层到底是哪几种不重要,所以乘f。
而在dp[i-1][j]里我们对于每一种方案里都已确定好颜色,每一个方案对应的本层都有特定的限制,所以需要钦定,用g乘。
代码短多了。时间也快了。
还有其实数组不用滚,用完直接覆盖就行。
1 #include<cstdio> 2 long long n,p,m,a[1000005],f[5005][5005],dp[5005],g[5005][5005],ld; 3 main(){ 4 scanf("%lld%lld%lld",&n,&m,&p);for(int i=1;i<=n;++i)scanf("%lld",&a[i]); 5 f[0][0]=ld=g[0][0]=1; 6 for(int i=1;i<=5000;++i) for(int j=i;j<=5000;++j) 7 f[i][j]=(f[i][j-1]*(i-1)+f[i-1][j-1]*(m+1-i))%p,g[i][j]=(g[i][j-1]*(i-1)+g[i-1][j-1]*i)%p; 8 for(int i=1;i<=n;++i,ld=dp[0],dp[0]=0) for(int j=1;j<=a[i]&&j<=m;++j) 9 (dp[0]+=(dp[j]=(ld*f[j][a[i]]-(j<=a[i-1])*dp[j]*g[j][a[i]])%p))%=p; 10 printf("%lld ",(ld+p)%p); 11 }