A+B Problem(FFT)

描述

给定 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;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值