Luogu P4463 [国家集训队] calc

WJMZBMR的题果然放在几年后看来仍然挺神,提出了一种独特的优化DP的方式

首先我们想一个暴力DP,先定下所有数的顺序(比如强制它递增),然后最后乘上(n!)种排列方式就是答案了

那么我们容易想出一个DP,令(f_{i,j})表示前(i)个数中,最大的数小于等于(j)的方案数是多少

显然有转移:

[f_{i,j}=f_{i-1,j-1} imes j+f_{i,j-1} ]

但这样DP是(O(nA))的,需要用拉格朗日插值进行优化,不会拉格朗日插值的可以看一下浅谈拉格朗日插值

在里面就提到了插值的一个关键点就是求出给定多项式的次数。因此我们观察(f_{n,i}),猜测它是关于(i)(t_n)次多项式

那么我们把转移方程移项一下:

[f_{i,j}-f_{i,j-1}=f_{i-1,j-1} imes j ]

有差分和乘积的一些相关性质可以得出它们之间次数的关系:

[t_n-1=t_{n-1}+1 ]

而当(n=0)时,有(t_0=0),所以容易推出(t_n=2n)

因此我们知道了(f_{n,i})是关于(i)(2n)次多项式,那么直接暴力DP求出(f_{n,1})(f_{n,2n+1}),然后插值出(f(n,A))的值即可

CODE

#include<cstdio>
#define RI register int
#define CI const int&
using namespace std;
const int N=505;
int A,n,m,f[N][N<<1],g[N<<1],fact[N<<1],mod;
inline int sum(CI a,CI b)
{
    int t=a+b; return t>=mod?t-mod:t;
}
inline int sub(CI a,CI b)
{
    int t=a-b; return t<0?t+mod:t;
}
inline int quick_pow(int x,int p=mod-2,int mul=1)
{
    for (;p;p>>=1,x=1LL*x*x%mod) if (p&1) mul=1LL*mul*x%mod; return mul;
}
inline void DP(CI n,CI m)
{
    RI i,j; for (RI i=0;i<=m;++i) f[0][i]=1;
    for (i=1;i<=n;++i) for (j=1;j<=m;++j)
    f[i][j]=sum(1LL*f[i-1][j-1]*j%mod,f[i][j-1]);
}
inline int Lagerange(CI n,int *f,CI k)
{
    if (k<=n) return f[k]; RI i; int ret=0,mt=1;
    for (i=1;i<=n;++i) mt=1LL*mt*(k-i)%mod; for (i=1;i<=n;++i)
    {
        int tp=1LL*mt*quick_pow(k-i)%mod*f[i]%mod;
        int dv=1LL*fact[i-1]*fact[n-i]%mod; tp=1LL*tp*quick_pow(dv)%mod;
        if ((n-i)&1) ret=sub(ret,tp); else ret=sum(ret,tp);
    }
    return ret;
}
int main()
{
    scanf("%d%d%d",&A,&n,&mod); DP(n,m=n<<1|1);
    RI i; for (i=1;i<=m;++i) g[i]=f[n][i];
    for (fact[0]=i=1;i<=m;++i) fact[i]=1LL*fact[i-1]*i%mod;
    return printf("%d",1LL*Lagerange(m,g,A)*fact[n]%mod),0;
}
原文地址:https://www.cnblogs.com/cjjsb/p/11055045.html