DP 长寿花

36 篇文章 0 订阅
36 篇文章 0 订阅

题面去内网找
设g[i][j]表示i个位置,用了j种颜色的方案数。因为a[i]max==5000,所以N^2预处理。
g[i][j]=g[i-1][j-1]* j+g[i-1][j]*(j-1)
解释一下,确定了最后一位,如果前面用了j-1种,那么他就有j种选择,但如果前面用了j种,就只有j-1种选择了。
f[i][j]表示到第i层,用了j种颜色。
f[i][j]=sigmaf[i-1][k]*g[i][j]*C(m,j)-g[i][j]*f[i-1][j]
前一半还是很好理解的。我考试时也是卡在了后一半,没想到怎么去重。当前行选了j种颜色,上一行如果重了那一定也是j种。所以把方案数减掉一遍就好了啊。
但组合数最恶心。p不是质数。。。可以分解要乘的数的质因数,搞个桶记录一下,最后累乘起来就行了。

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<iostream>
#define N 1000005
#define ll long long
using namespace std;
int n,m,h,a[N];
int tot,mark[N],pri[N];
ll p,C[5005],tong[N],f[2][5005],g[5005][5005];
void get_pri()
{
     for(int i=2;i<=m;i++)
     {
         if(!mark[i])pri[++tot]=i;
         for(int j=1;j<=tot;j++)
         {
             if(i*pri[j]>m)break;
             mark[i*pri[j]]=1;
             if(i%pri[j]==0)break;
         }
     }
}
void fj(int x,int k)
{
     for(int i=1;i<=tot&&pri[i]<=x;i++)
     {
          if(x==1)break;
          if(x%pri[i])continue;
          while(x%pri[i]==0)x/=pri[i],tong[i]+=k;
     }
}
void init()
{
     g[0][0]=1;
     for(int i=1;i<=h;i++)
        for(ll j=1;j<=min(m,h);j++)
           g[i][j]=(g[i-1][j-1]*j%p+g[i-1][j]*(j-1)%p)%p;
     get_pri();
     for(int i=1;i<=min(m,h);i++)
     {
          C[i]=1;
          fj(i,-1);fj(m-i+1,1);
          for(int j=1;j<=tot&&pri[j]<=m;j++)
              for(int k=1;k<=tong[j];k++)
                  C[i]=(C[i]*pri[j])%p;
     }
}
int main()
{
    scanf("%d%d%lld",&n,&m,&p);
    for(int i=1;i<=n;i++)scanf("%d",&a[i]),h=max(h,a[i]);
    init();f[0][0]=1;
    for(int i=1;i<=n;i++)
    {
        int x=i&1;f[x][0]=0;
        for(int j=1;j<=min(m,a[i]);j++)
        {
            f[x][j]=(f[x^1][0]*g[a[i]][j]%p)*C[j]%p;
            if(a[i-1]>=j)f[x][j]=(f[x][j]-f[x^1][j]*g[a[i]][j]%p+p)%p;
            f[x][0]=(f[x][0]+f[x][j])%p;
        }
    }
    printf("%lld\n",f[n&1][0]);
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值