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\n";
#else
    const char LL[]="%lld\n";
#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\n";
#else
    const char LL[]="%lld\n";
#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;
}

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

猜你喜欢

转载自www.cnblogs.com/Gloid/p/9465662.html