• BZOJ1042 HAOI2008硬币购物(任意模数NTT+多项式求逆+生成函数/容斥原理+动态规划)


      第一眼生成函数。四个等比数列形式的多项式相乘,可以化成四个分式。其中分母部分是固定的,可以多项式求逆预处理出来。而分子部分由于项数很少,询问时2^4算一下贡献就好了。这个思路比较直观。只是常数巨大,以及需要敲一发类似任意模数ntt的东西来避免爆精度。成功以这种做法拿下luogu倒数rank1,至于bzoj不指望能过了。

    #include<iostream> 
    #include<cstdio>
    #include<cmath>
    #include<cstdlib>
    #include<cstring>
    #include<algorithm>
    #include<iomanip>
    using namespace std;
    int read()
    {
        int x=0,f=1;char c=getchar();
        while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();}
        while (c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
        return x*f;
    }
    #define N 550000
    #define T 100000
    #define P1 998244353
    #define P2 1004535809
    int r[N],c1,c2,c3,c4,tot,d1,d2,d3,d4,s,t;
    int a[N],b[N],c[N],e[2][N];
    long long f[N];
    void inc(int &x,int P){x++;if (x>=P) x-=P;}
    void dec(int &x,int P){x--;if (x<0) x+=P;}
    int ksm(int a,int k,int P)
    {
        if (a==0) return 0;
        if (k==0) return 1;
        int tmp=ksm(a,k>>1,P);
        if (k&1) return 1ll*tmp*tmp%P*a%P;
        else return 1ll*tmp*tmp%P;
    }
    long long ksc(long long a,long long b,long long P)
    {
        long long t=a*b-(long long)((long double)a*b/P+0.5)*P;
        return t<0?t+P:t;
    }
    void DFT(int n,int *a,int p,int P)
    {
        for (int i=0;i<n;i++) if (i<r[i]) swap(a[i],a[r[i]]);
        for (register int i=2;i<=n;i<<=1)
        {
            int wn=ksm(p,(P-1)/i,P);
            for (register int j=0;j<n;j+=i)
            {
                int w=1;
                for (register int k=j;k<j+(i>>1);k++,w=1ll*w*wn%P)
                {
                    int x=a[k],y=1ll*w*a[k+(i>>1)]%P;
                    a[k]=(x+y)%P,a[k+(i>>1)]=(x-y+P)%P;
                }
            }
        }
    }
    void mul(int n,int *a,int *b,int P,int inv3)
    {
        DFT(n,a,3,P),DFT(n,b,3,P);
        for (int i=0;i<n;i++) a[i]=1ll*a[i]*(P+2-1ll*a[i]*b[i]%P)%P;
        DFT(n,a,inv3,P);
        int inv=ksm(n,P-2,P);
        for (int i=0;i<n;i++) a[i]=1ll*a[i]*inv%P;
    }
    void solve(int P,int inv3,int op)
    {
        memset(a,0,sizeof(a));
        memset(b,0,sizeof(b));
        memset(c,0,sizeof(c));
        if (c1+c2+c3+c4<=T) inc(a[c1+c2+c3+c4],P);
        if (c1+c2+c3<=T) dec(a[c1+c2+c3],P);
        if (c1+c2+c4<=T) dec(a[c1+c2+c4],P);
        if (c4+c2+c3<=T) dec(a[c4+c2+c3],P);
        if (c1+c4+c3<=T) dec(a[c1+c4+c3],P);
        if (c1+c2<=T) inc(a[c1+c2],P);
        if (c1+c3<=T) inc(a[c1+c3],P);
        if (c1+c4<=T) inc(a[c1+c4],P);
        if (c2+c3<=T) inc(a[c2+c3],P);
        if (c4+c2<=T) inc(a[c4+c2],P);
        if (c3+c4<=T) inc(a[c3+c4],P);
        dec(a[c1],P);dec(a[c2],P);dec(a[c3],P);dec(a[c4],P);
        inc(a[0],P);
        t=1;b[0]=1;
        while (t<=T)
        {
            t<<=1;
            for (int i=0;i<t;i++) c[i]=a[i];
            for (int i=0;i<(t<<1);i++) r[i]=(r[i>>1]>>1)|(i&1)*t;
            mul(t<<1,b,c,P,inv3);
            for (int i=t;i<(t<<1);i++) b[i]=0;
        }
        memcpy(e[op],b,sizeof(e[op]));
    }
    void crt()
    {
        long long P=1ll*P1*P2,inv1=ksm(P2%P1,P1-2,P1),inv2=ksm(P1%P2,P2-2,P2);
        for (int i=0;i<=T;i++)
        f[i]=(ksc(1ll*e[0][i]*P2%P,inv1,P)+ksc(1ll*e[1][i]*P1%P,inv2,P))%P;
    }
    int main()
    {
    #ifndef ONLINE_JUDGE
        freopen("bzoj1042.in","r",stdin);
        freopen("bzoj1042.out","w",stdout);
        const char LL[]="%I64d
    ";
    #else
        const char LL[]="%lld
    ";
    #endif
        c1=read(),c2=read(),c3=read(),c4=read(),tot=read();
        solve(P1,332748118,0);
        solve(P2,334845270,1);
        crt();
        while (tot--)
        {
            d1=read(),d2=read(),d3=read(),d4=read(),s=read();
            d1=min(1ll*s+1,1ll*(d1+1)*c1);
            d2=min(1ll*s+1,1ll*(d2+1)*c2);
            d3=min(1ll*s+1,1ll*(d3+1)*c3);
            d4=min(1ll*s+1,1ll*(d4+1)*c4);
            long long ans=f[s];
            if (d1+d2+d3+d4<=s) ans+=f[s-(d1+d2+d3+d4)];
            if (d1+d2+d3<=s) ans-=f[s-(d1+d2+d3)];
            if (d1+d2+d4<=s) ans-=f[s-(d1+d2+d4)];
            if (d4+d2+d3<=s) ans-=f[s-(d4+d2+d3)];
            if (d1+d4+d3<=s) ans-=f[s-(d1+d4+d3)];
            if (d1+d2<=s) ans+=f[s-(d1+d2)];
            if (d1+d3<=s) ans+=f[s-(d1+d3)];
            if (d1+d4<=s) ans+=f[s-(d1+d4)];
            if (d2+d3<=s) ans+=f[s-(d2+d3)];
            if (d4+d2<=s) ans+=f[s-(d4+d2)];
            if (d3+d4<=s) ans+=f[s-(d3+d4)];
            if (d1<=s) ans-=f[s-d1];
            if (d2<=s) ans-=f[s-d2];
            if (d3<=s) ans-=f[s-d3];
            if (d4<=s) ans-=f[s-d4];
            printf(LL,ans);
        }
        return 0;
    }

      还有一种更优秀的做法。考虑如果硬币没有个数限制的话,就是一个完全背包。添加限制可以想到容斥。我们枚举有哪几种硬币超过了个数限制,就可以容斥斥斥容容容斥把多重背包转化成完全背包了。

    #include<iostream> 
    #include<cstdio>
    #include<cmath>
    #include<cstdlib>
    #include<cstring>
    #include<algorithm>
    #include<iomanip>
    using namespace std;
    int read()
    {
        int x=0,f=1;char c=getchar();
        while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();}
        while (c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
        return x*f;
    }
    #define N 100010
    #define ll long long
    int c[4],t,d[4],s;
    ll f[N],ans;
    int calc(int k,int x){if (k<x) return 0;else return f[k-x];}
    void dfs(int k,int sum,ll tot)
    {
        if (tot>s) return;
        if (k==4) {ans+=((sum&1)?-1:1)*f[s-tot];return;} 
        dfs(k+1,sum+1,tot+1ll*(d[k]+1)*c[k]);
        dfs(k+1,sum,tot);
    }
    int main()
    {
    #ifndef ONLINE_JUDGE
        freopen("bzoj1042.in","r",stdin);
        freopen("bzoj1042.out","w",stdout);
        const char LL[]="%I64d
    ";
    #else
        const char LL[]="%lld
    ";
    #endif
        for (int i=0;i<4;i++) c[i]=read();
        t=read();
        f[0]=1;
        for (int i=0;i<4;i++)
            for (int j=c[i];j<=N-10;j++)
            f[j]+=f[j-c[i]];
        while (t--)
        {
            for (int i=0;i<4;i++) d[i]=read();
            s=read();
            ans=0;
            dfs(0,0,0);
            printf(LL,ans);
        }
        return 0;
    }

       仔细考虑一下会发现两个做法本质上其实是一样的。分子部分所乘的多项式就是一个容斥的过程,而求逆所得的结果就是完全背包。

  • 相关阅读:
    程序怎么才能把自己的删除掉?
    Winsock编程入门1.初始化Winsock
    关于83版射雕英雄传
    一个感人的爱情故事(中英对照)
    NT系统的命令
    Delphi小巧的Windows NT服务程序源码
    更改Windows 登录屏幕保护程序
    画鬼最易
    濮水垂钓
    现代工作观
  • 原文地址:https://www.cnblogs.com/Gloid/p/9465662.html
Copyright © 2020-2023  润新知