构造出原序列的生成函数
A
A
,它的三次方就是损失的方案数
可惜题目要求方案互不相同,这样计算会有重复的方案出现
那我们可以容斥一发
令表示所有物品选一个的生成函数,
B
B
表示一次选俩的生成函数,表示一次选仨的生成函数
手动容斥一发
拿一个的方案数:
A
A
拿两个的方案数:,
A∗A
A
∗
A
就是带重复的选两个,重复一共有两种:第一种是两个一样的拼到一起,减去
B
B
就可以解决,还有一种就是将和
y+x
y
+
x
算了两次,除
2
2
就OK
拿三个的方案数:,这个和上一个差不多,感性理解下就可以了(实在不行画个图模拟一下乘法的过程)
代码如下:
#include<algorithm>
#include<ctype.h>
#include<cstdio>
#include<cmath>
#define N 550050
using namespace std;
const double DFT=2.0,IDFT=-2.0;
const double pi=acos(-1);
inline int read(){
int x=0,f=1;char c;
do c=getchar(),f=c=='-'?-1:f; while(!isdigit(c));
do x=(x<<3)+(x<<1)+c-'0',c=getchar(); while(isdigit(c));
return x*f;
}
struct Complex{
double x,y;
Complex(){}
Complex(double _,double __):x(_),y(__){}
Complex operator + (Complex b) const{return Complex(x+b.x,y+b.y);}
Complex operator - (Complex b) const{return Complex(x-b.x,y-b.y);}
Complex operator * (Complex b) const{return Complex(x*b.x-y*b.y,x*b.y+y*b.x);}
Complex operator * (double b) const{return Complex(x*b,y*b);}
Complex operator / (double b) const{return Complex(x/b,y/b);}
}a[N],b[N],c[N],d[N];
int pos[N];
int n,x,len,maxx;
inline void FFT(Complex a[],double mode){
for(int i=0;i<len;i++)
if(i<pos[i])
swap(a[i],a[pos[i]]);
for(int i=2,mid=1;i<=len;i<<=1,mid<<=1){
Complex wm(cos(2.0*pi/i),sin(mode*pi/i));
for(int j=0;j<len;j+=i){
Complex w(1,0);
for(int k=j;k<j+mid;k++,w=w*wm){
Complex l=a[k],r=w*a[k+mid];
a[k]=l+r;a[k+mid]=l-r;
}
}
}
if(mode==IDFT)
for(int i=0;i<len;i++)
a[i].x/=len;
return;
}
int main(){
n=read();
for(int i=1;i<=n;i++){
x=read();
a[x].x++;b[x*2].x++;c[x*3].x++;
maxx=max(maxx,x*3);
}
for(len=1;len<maxx<<1;len<<=1);
for(int i=0;i<len;i++){
pos[i]=pos[i>>1]>>1;
if(i&1) pos[i]|=len>>1;
}
FFT(a,DFT);FFT(b,DFT);FFT(c,DFT);
for(int i=0;i<len;i++)
d[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(d,IDFT);
for(int i=1;i<=len;i++){
int k=int(d[i].x+0.1);
if(k) printf("%d %d\n",i,k);
}
return 0;
}