BZOJ2655calc

题目描述

一个序列a1,...,an是合法的,当且仅当:
长度为给定的n。
a1,...,an都是[1,A]中的整数。
a1,...,an互不相等。
一个序列的值定义为它里面所有数的乘积,即a1a2...an。
求所有不同合法序列的值的和。
两个序列不同当且仅当他们任意一位不一样。
输出答案对一个数mod取余的结果。

题解

先考虑dp

我们设dp[i][j]表示前i个元素,已经填完了1~j的所有数字,它们的价值和是多少。

转移:dp[i][j]=dp[i][j-1]+dp[i-1][j-1]*j

第一维比较小,但第二维比较大,所以需要优化。

然后考虑dp[n]是一个x次多项式。

观察转移的形式,dp[i][j]-dp[i][j-1]=dp[i-1][j-1]*j

前面的是一个差分的形式,多项式的次数会-1。

G[n]-1=G[n-1]+1

又因为G[0]=1,所以x=2n

所以可以先求出2*n+1项,再上拉格朗日插值。

代码

#include<iostream>
#include<cstdio>
#define N 1009
using namespace std;
typedef long long ll;
int n,m,q;
ll mod,a[N],f[N][N],A;
inline ll rd(){
    ll x=0;char c=getchar();bool f=0;
    while(!isdigit(c)){if(c=='-')f=1;c=getchar();}
    while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
    return f?-x:x;
}
inline ll power(ll x,ll y){
    ll ans=1;
    while(y){
        if(y&1)ans=ans*x%mod;x=x*x%mod;y>>=1;
    }
    return ans;
}
inline ll ni(ll x){return power(x,mod-2);}
inline ll work(int x){
    if(x<=m)return a[x];
    ll ans=0;
    for(int i=1;i<=m;++i){
        ll res=a[i];
        for(int j=1;j<=m;++j){
            if(j!=i)res=res*(x-j)%mod*ni(i-j)%mod;
        }    
        res=(res+mod)%mod;
        ans=(ans+res)%mod;
    }
    return ans;
}
int main(){
    A=rd();n=rd();mod=rd();
    m=n*2+1;
    for(int i=0;i<=m;++i)f[0][i]=1;
    for(int i=1;i<=n;++i)
      for(int j=0;j<=m;++j)f[i][j]=(f[i-1][j-1]*j%mod+f[i][j-1])%mod;
    for(int i=1;i<=m;++i)a[i]=f[n][i];
    ll ans=1;
    for(int i=1;i<=n;++i)ans=ans*i%mod;
    printf("%lld",ans*work(A)%mod);
    return 0;
}
posted @ 2019-02-27 16:39  comld  阅读(230)  评论(0编辑  收藏  举报