[生成函数+容斥+FFT] BZOJ3771: Triple

题意

给出n个物品,每个物品有一个价值Ai。可以选一个或两个或三个,求每种可能的总价值的选取方案。
Ai40000

题解

题面好有趣……
考虑构造普通型生成函数A(x),表示取一个的方案。
答案肯定不能直接 A3+A2+A ,因为一个物品可能被取了多次,考虑如何去重。
取一个就是 A 没错,
取两个是(A2B)/2 B 是取相同的两个的生成函数。
取三个是(A3AB3+C2)/6 C 是取相同的三个的生成函数。
其中AB3 能除去取3个里有两个相同的情况,但多减了两倍的3个都相同的情况。
所以答案即是:

A+(A2B)/2+(A3AB3+C2)/6

次数界较大,需要用FFT加速多项式乘法。

#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
const int maxn=300005;
const double PI=acos(-1);
int m,n;
struct E{
    double real,imag;
    E(double t1=0,double t2=0){ real=t1; imag=t2; }
    void operator /= (const int &val){ real/=val; imag/=val; }
};
E operator + (const E &A,const E &B){ return E(A.real+B.real,A.imag+B.imag); }  
E operator - (const E &A,const E &B){ return E(A.real-B.real,A.imag-B.imag); }  
E operator * (const E &A,const E &B){ return E(A.real*B.real-A.imag*B.imag,A.imag*B.real+A.real*B.imag); }
E operator / (const E &A,const int &x){ return E(A.real/x,A.imag/x); }
int rev[maxn];
void get_rev(int n){
    rev[0]=0; int log2n=log2(n);
    for(int i=1;i<=n-1;i++) rev[i]=(rev[i>>1]>>1)|((i&1)<<(log2n-1));
}
void FFT(E a[],int n,int _k){
    for(int i=0;i<=n-1;i++) if (i<rev[i]) swap(a[i],a[rev[i]]);
    for(int m=2;m<=n;m<<=1){
        E wm(cos(2*PI/m),_k*sin(2*PI/m));
        for(int k=0;k<=n-1;k+=m){
            E w(1,0),t0,t1;
            for(int j=0;j<=m/2-1;j++,w=w*wm) t0=a[k+j], t1=w*a[k+j+m/2], a[k+j]=t0+t1, a[k+j+m/2]=t0-t1;    
        } 
    }
    if(_k==-1) for(int i=0;i<=n-1;i++) a[i]/=n;
}
E a[maxn],b[maxn],c[maxn],ans[maxn];
int main(){
    freopen("bzoj3771.in","r",stdin);
    freopen("bzoj3771.out","w",stdout);
    scanf("%d",&m);
    while(m--){
        int x; scanf("%d",&x); n=max(n,x*3);
        a[x].real++; b[x*2].real++; c[x*3].real++;
    }
    int _n=1; while(_n<n) _n<<=1; n=_n<<1;
    get_rev(n);
    FFT(a,n,1); FFT(b,n,1); FFT(c,n,1);
    for(int i=0;i<=n-1;i++) ans[i]=a[i]+(a[i]*a[i]-b[i])/2+(a[i]*a[i]*a[i]-a[i]*b[i]*3+c[i]*2)/6;
    FFT(ans,n,-1);
    for(int i=0;i<=n-1;i++) if((int)(ans[i].real+0.5)>0) printf("%d %d\n",i,(int)(ans[i].real+0.5));
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值