题意
给出n个物品,每个物品有一个价值Ai。可以选一个或两个或三个,求每种可能的总价值的选取方案。
Ai≤40000
题解
题面好有趣……
考虑构造普通型生成函数A(x),表示取一个的方案。
答案肯定不能直接
A3+A2+A
,因为一个物品可能被取了多次,考虑如何去重。
取一个就是
A
没错,
取两个是
取三个是
其中
所以答案即是:
A+(A2−B)/2+(A3−A∗B∗3+C∗2)/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;
}