【NOIP模拟赛】长寿花(组合计数,DP去重)

asdf n , m < = 1 e 6 , a i < = 5 e 3 , ∑ a i < = 1 e 6 n,m<=1e6,a_i<=5e3,\sum a_i <= 1e6 n,m<=1e6,ai<=5e3,ai<=1e6
不 用 谢 。 \text 不用谢。

首先每一层的答案只与颜色的数量有关。
但是相邻层的颜色集合不能相同。
预处理 g i , j g_{i,j} gi,j表示 i i i个位置分配给 j j j种颜色并且每种都出现并且相邻的颜色不同的方案数,注意我们让 g g g中统计的方案颜色无标号之分,也就是 g i , j = g i − 1 , j − 1 + g i − 1 , j ∗ ( j − 1 ) g_{i,j} = g_{i-1,j-1} + g_{i-1,j}*(j-1) gi,j=gi1,j1+gi1,j(j1),这样可以在之后的 D P \rm DP DP中可以免去求逆元的环节。
f i , j f_{i,j} fi,j表示前 i i i层,第 i i i层用了 j j j种颜色的方案。
f i , j = g a i , j ( m j ) j ! ∑ k f i − 1 , k − f i − 1 , j ∗ g i − 1 , j j ! f_{i,j} = g_{a_i,j}\binom mj j!\sum_{k}f_{i-1,k} - f_{i-1,j}*g_{i-1,j}j! fi,j=gai,j(jm)j!kfi1,kfi1,jgi1,jj!
这里的 ( m j ) j ! \binom mj j! (jm)j!可以简单预处理递推得到而不用求逆元。

A C   C o d e \rm AC\ Code AC Code

#include<bits/stdc++.h>
#define maxn 10000005
using namespace std;
 
int n,m,p;
int a[maxn],g[5005][5005],f[2][5005],Am[5005],fac[5005];
 
int main(){
//  freopen("1.in","r",stdin);
//  freopen("1.out","w",stdout);
    scanf("%d%d%d",&n,&m,&p);
    g[0][0] = 1;
    for(int i=1;i<=5000;i++)
        for(int j=1;j<=i && j<=m;j++)
            g[i][j] = ((j ? g[i-1][j-1] : 0) + 1ll * g[i-1][j] * (j-1)) % p;
    Am[0] = fac[0] = 1;
    for(int i=1;i<=min(5000,m);i++) Am[i] = 1ll * Am[i-1] * (m-i+1) % p , fac[i] = 1ll * fac[i-1] * i % p;
    int now = 1 , pre = 0 , sum = 0;f[now][0] = sum = 1;
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        swap(now,pre);
        for(int j=0;j<=a[i];j++)
            f[now][j] = sum * 1ll * g[a[i]][j] % p * Am[j] % p; 
        for(int j=0;j<=a[i-1];j++) if(f[pre][j]){
            f[now][j] = (f[now][j] - 1ll * g[a[i]][j] * f[pre][j] % p * fac[j]) % p;
            f[pre][j] = 0;
        }
        sum = 0;
        for(int j=0;j<=a[i];j++) sum = (sum + f[now][j]) % p /* , printf("%d %d %d\n",i,j,f[now][j])*/;
    }
    printf("%d\n",(sum+p)%p);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值