calc(陈立杰) 解题报告

35 篇文章 1 订阅
4 篇文章 0 订阅

感觉最近状态真是烂到爆。。

首先不妨令序列有序,然后再乘n!即可。

一上来先想到可以倍增,设f[i][j]表示在1~j中选i个数,那么有j->2j,便可以通过枚举一边选了几个得到。但是算错了复杂度以为是 O(n3logn) 的。。(矩阵乘习惯了一倍增就感觉是三方挂logn。。)(没想到看了题解以后这竟然就是标算。。)

然后又想,既然倍增是三次方的。。。那只能看看暴力转移了。 f(i,j)=jj1k=1f(i1,k) ,即f(i,j)=j*s(i-1,j-1)。那么不妨猜测f(i,x)是一个关于x的若干项多项式,而通过这个递推式可以发现f(i,x)会比f(i-1,x)次数大2,前缀和会使其次数+1,然后它又乘了一个x,而当i=1的时候我们知道它确实等于 x(x+1)2 ,是一个3次多项式,即最终答案会是一个2n+1次多项式。那么我们不妨dp求出f(n,1~2n+1)以内的值,然后拉格朗日插值即可。

随后又想到了一个容斥做法。(终于明白容斥是干什么啦!)
设f(i)为最终答案,则有 f(i)=i1j=0(1)ij1(Ak=1kj)f(j)i 。这是因为考虑每个数k,首先加上它出现1或2次的方案数,再减去它出现2或3次的方案数,再加上它出现3或4次的方案数。。直到最后,但是这样的话一种方案数中i个数都会被考虑从而计算一遍,所以每种方案都被考虑了i遍,那么最后除以i就可以了。
这个其实也不能算容斥啦。一般的容斥应该是什么样的呢?应该是算至少出现多少次的方案数,那么从小到大考虑至少出现次数,计算至少出现i次的时候,它的系数应该等于一个方案之前被统计次数的相反数,因为至少出现i次在之后不可能被考虑到了,而最终这种方案的系数需要是0,那么现在它就必须是0!

代码(拉格朗日插值):

#include<cstdio>
#include<iostream>
using namespace std;
#include<algorithm>
#include<cstring>
#include<cmath>
const int A=1e9+5,N=500+5,P=1e9+5;
int p;
typedef long long LL;
LL power(LL prod,int x)
{
    LL ans=1;
    for(;x;x>>=1)
    {
        if(x&1)(ans*=prod)%=p;
        (prod*=prod)%=p;
    }
    return ans;
}
LL f[N][N<<1];
int main()
{
    freopen("local4.in","r",stdin);
    //freopen("local4.out","w",stdout);
    int a,n;
    scanf("%d%d%d",&a,&n,&p);
    int m=n+1;
    for(int j=1;j<=m;++j)f[1][j]=j+f[1][j-1];
    for(int i=2;i<=n;++i)
        for(int j=i;j<=m;++j)
        {
            f[i][j]=(j*f[i-1][j-1]+f[i][j-1])%p;

            //printf("f(%d,%d)=%I64d\n",i,j,f[i][j]);
        }
    LL ans=0;
    if(a<=m)ans=f[n][a];
    else
    {
        LL prod=1;
        for(int i=2;i<=m;++i)prod=prod*(a-i)%p*power(1-i,p-2)%p;
        (ans+=f[n][1]*prod)%=p;

        //printf("[i=1]:%I64d\n",(f[n][1]*prod%p+p)%p);

        for(int i=2;i<=m;++i)
        {
            prod=prod*(a-(i-1))%p*power(a-i,p-2)%p*power(i-1,p-2)%p*(i-1-m)%p;
            (ans+=f[n][i]*prod)%=p;

            //printf("[i=%d]:%I64d\n",i,(f[n][i]*prod%p+p)%p);
        }
        ans=(ans+p)%p;
    }

    //printf("ans=%I64d\n",ans);

    for(int i=n;i;--i)(ans*=i)%=p;
    cout<<ans<<endl;
}

代码(容斥):

#include<stdio.h>
#include<iostream>
using namespace std;
typedef long long LL;
const int N=500+5;
LL com[N][N],f[N],coe[N];
int p;
LL power(LL prod,int x)
{
    LL ans=1;
    for(;x;x>>=1)
    {
        if(x&1)(ans*=prod)%=p;
        (prod*=prod)%=p;
    }
    return ans;
}
int main()
{
    freopen("local4.in","r",stdin);
    int a,n;
    scanf("%d%d%d",&a,&n,&p);

    for(int i=n+1;i>=0;--i)com[i][0]=1;
    for(int i=1;i<=n+1;++i)
        for(int j=i;j;--j)
            com[i][j]=(com[i-1][j-1]+com[i-1][j])%p;

    coe[0]=a;
    for(int i=1;i<=n;++i)coe[i]=coe[i-1]*a%p;
    for(int i=1;i<=n;++i)
    {
        for(int j=i-1,sgn=1;j>=0;--j,sgn=-sgn)(coe[i]+=sgn*com[i+1][j]*coe[j])%=p;
        (coe[i]*=power(i+1,p-2))%=p;

        //printf("coe[%d]=%I64d\n",i,coe[i]);
    }

    f[0]=1;
    for(int i=1;i<=n;++i)
    {
        for(int j=i-1,sgn=1;j>=0;--j,sgn=-sgn)(f[i]+=sgn*coe[i-j]*f[j])%=p;
        (f[i]*=power(i,p-2))%=p;

        //printf("f[%d]=%d\n",i,f[i]);
    }

    LL ans=(f[n]+p)%p;
    for(int i=n;i;--i)(ans*=i)%=p;
    cout<<ans<<endl;
}

总结:
①一定要认真算时间复杂度!
②对于有一项特别大的dp,也不一定非要矩乘/倍增呀,说不定是多项式!
③容斥就是说从大到小枚举集合,保证当前集合系数是0.
④如果点选取得当,比如x是1~n,拉格朗日插值是可以做到O(n)的。
⑤注意有序和无序之间的相互转化,统计有序的时候可以考虑无序,而无序的时候又可以先考虑有序。

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值