描述
给定 n 个整数,问有多少种方式选择三个整数 (i,j,k),使得 ai+aj=ak(i≠j≠k)?两种方式不同当且仅当有序三元组(i,j,k)的元素至少有一个不同。
输入
第一行为一个整数T,表示测试数据组数。对于每组测试数据:第一行为一个整数n。第二行为n个整数a1,a2,…,an中间以空格分割。
T≤10
1≤n≤2⋅105
|ai|≤500
输出
对于每组测试数据,输出“Case #x: y”。其中x为测试数据编号(从1开始),y为一个整数表示不同的选择方式个数。
样例输入1
2 4 1 2 3 4 6 1 1 3 3 4 6
样例输出1
Case #1: 4 Case #2: 10
题目链接:http://acmoj.shu.edu.cn/problem/449/
思路:要求满足a[i]+a[j]=a[k]的数量,首先想到的可能是暴力相加来求,但复杂度太高。其实加法正好对应于乘法的幂运算(两个底数相同的数相乘,结果为指数相加,底数不变),所以考虑将a[i]转变为某个多项式的幂,构造两个相同的多项式相乘即可,相乘以后,等价于完成了一次加法,然后去看卷积的结果。
例如:2 2 3
可以转换成 (2*x^2+ x^3)*(2*x^2+ x^3) 相乘,最后得到(4*x^4+4*x^5+x^6),再去查幂次为2,3的项对应的系数,就是答案。当然正确结果肯定要考虑去重,比如,自己和自己相乘,0和0,0和a[i]。去重的主要原因是因为输入可能有负数,我们需要进行hash操作,导致了,实际运算中,其实没有x^0出现。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef long long ll;
const int MAXN= 2e5+10;
const int maxn=2e5+10;
const double PI = acos(-1.0);
struct Complex
{
double x,y;
inline Complex operator +(const Complex b)const {return (Complex){x+b.x,y+b.y};}
inline Complex operator -(const Complex b)const {return (Complex){x-b.x,y-b.y}; }
inline Complex operator *(const Complex b)const {return (Complex){x*b.x-y*b.y,x*b.y+y*b.x};}
}va[MAXN*2+MAXN/2],vb[MAXN*2+MAXN/2];
int lenth=1, rev[MAXN*2+MAXN/2];
int N, M; //f和g的数量
int f[MAXN], g[MAXN]; //f和g的系数
vector<LL> conv; //卷积结果
vector<LL> multi; //⼤数乘积
void init()
{
int tim=0; lenth = 1;
conv.clear(), multi.clear();
memset( va , 0 , sizeof va);
memset( vb , 0 , sizeof vb);
while( lenth <= N+M-2 ) lenth<<=1,tim++;
for( int i=0;i<lenth;i++)
rev[i]=(rev[i>>1]>>1)+((i&1)<<(tim-1));
}
void FFT(Complex*A,const int fla)
{
for( int i=0;i<lenth;i++)
{
if(i<rev[i])
{
swap(A[i],A[rev[i]]);
}
}
for( int i=1;i<lenth;i<<=1)
{
const Complex w = (Complex){cos(PI/i),fla*sin(PI/i)};
for( int j=0;j<lenth;j+=(i<<1))
{
Complex K=(Complex){1,0};
for( int k=0;k<i;k++,K=K*w)
{
const Complex x=A[j+k],y=K*A[j+k+i];
A[j+k]=x+y;
A[j+k+i]=x-y;
}
}
}
}
void getConv()
{
init();
for( int i = 0 ; i < N; i++ ) va[i].x = f[i];
for( int i = 0 ; i < M ; i++) vb[i].x = g[i];
FFT(va,1),FFT(vb,1);
for( int i=0;i<lenth;i++) va[i]=va[i]*vb[i];
FFT(va,-1);
for( int i = 0; i <= N+M-2 ; i++) conv.push_back((LL)(va[i].x/lenth+0.5));
}
void getMulti()
{
getConv();
multi = conv;
reverse(multi.begin(), multi.end());
multi.push_back(0);
for( int i = 0; i < multi.size()-1 ; i++)
{
multi[i+1] += multi[i]/10;
multi[i] %= 10;
}
while(!multi.back() && multi.size() > 1) multi.pop_back();
reverse(multi.begin(), multi.end());
}
//事先需要设置系数f和g和数组⼤⼩N和M
//卷积结果保存conv, 乘法结果保存mult
int n;
int cnt[1500];
int a[maxn];
int main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
int T;
cin>>T;
int Case=0;
while(T--)
{
scanf("%d",&n);
memset(cnt,0,sizeof(cnt));
int mx=0;
int cnt0=0;
for(int i=1;i<=n;i++)
{
int u;
scanf("%d",&u);
a[i]=u;
cnt[u+550]++;
mx=max(mx,u+550);
if(u==0) cnt0++;
}
N=M=mx+1;
for(int i=0;i<=mx+1;i++)
{
g[i]=f[i]=cnt[i];
}
getConv();
for(int i=1;i<=n;i++)
{
conv[(a[i]+550)*2]--;
}
ll ans=0;
for(int i=1;i<=n;i++)
{
ans+=conv[(a[i]+550*2)];
if(a[i]) ans -=cnt0*2;//0和a[i]
else ans -=(cnt0-1)*2;//0和0
}
printf("Case #%d: ",++Case);
printf("%lld\n",ans);
}
return 0;
}