Luogu P4463 [国家集训队] calc

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

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

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

显然有转移:

\[f_{i,j}=f_{i-1,j-1}\times 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}\times 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;
}
posted @ 2019-06-19 21:27 hl666 阅读(...) 评论(...) 编辑 收藏