Asia Hong Kong Online Preliminary A (FFT)

题目链接:点击这里

题意:给出n个数字( [50000,50000] ),求有多少(i,j,k)满足 ai+aj=ak (要求i,j,k互不相同)。

0比较麻烦直接拎出来记录个数,其他的加一个50000避免负数直接存起来。然后开一个数组记录每一个数字出现多少次,这个数组和自己做一次卷积即可。答案分成几部分:
1. i+j=k: 直接遍历扔掉0的数组,把算完卷积的结果扔进去;
2. 0+0=0:直接用0的个数可以得出结果;
3. x+y=0:直接用卷积中0次项的系数 或者暴力遍历;
4. 0+x=x/x+0=x:暴力遍历统计。

#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <queue>
#include <map>
using namespace std;
#define pi acos (-1)  
#define maxn 511111
#define add 50000

struct plex {  
    double x, y;  
    plex (double _x = 0.0, double _y = 0.0) : x (_x), y (_y) {}  
    plex operator + (const plex &a) const {  
        return plex (x+a.x, y+a.y);  
    }  
    plex operator - (const plex &a) const {  
        return plex (x-a.x, y-a.y);  
    }  
    plex operator * (const plex &a) const {  
        return plex (x*a.x-y*a.y, x*a.y+y*a.x);  
    }  
}x[maxn];  

void change (plex *y, int len) {  
    int i, j, k;  
    for(i = 1, j = len / 2; i < len - 1; i++) {  
        if (i < j) swap(y[i], y[j]);  
        k = len / 2;  
        while (j >= k) {  
            j -= k;  
            k /= 2;  
        }  
        if (j < k) j += k;  
    }  
}  

void fft(plex y[],int len,int on)  
{  
    change(y,len);  
    for(int h = 2; h <= len; h <<= 1)  
    {  
        plex wn(cos(-on*2*pi/h),sin(-on*2*pi/h));  
        for(int j = 0;j < len;j+=h)  
        {  
            plex w(1,0);  
            for(int k = j;k < j+h/2;k++)  
            {  
                plex u = y[k];  
                plex t = w*y[k+h/2];  
                y[k] = u+t;  
                y[k+h/2] = u-t;  
                w = w*wn;  
            }  
        }  
    }  
    if(on == -1)  
        for(int i = 0;i < len;i++)  
            y[i].x /= len;  
}  

int n, cnt, zero;
int a[maxn], num[maxn];

long long solve () {
    long long ans = 0;
    int len = 1;
    while (len < 200000) {
        len <<= 1;
    }
    for (int i = 0; i < len; i++) {
        x[i] = plex (num[i], 0);
    }
    fft (x, len, 1);
    for (int i = 0; i < len; i++) {
        x[i] = x[i]*x[i];
    }
    fft (x, len, -1);

    for (int i = 1; i <= cnt; i++) {//i+j=k
        long long tmp = (long long) (x[a[i]+add*2].x + 0.5);
        ans += tmp;
    }
    for (int i = 1; i <= cnt; i++) {//减去i+i=j
        if (a[i]%2 == 0)
            ans -= num[a[i]/2+add];
    }

    if (zero >= 3) ans += 1LL*zero*(zero-1)*(zero-2);//0+0=0
    for (int i = 0; i <= add*2; i++) {//x+0=y y+0=x
        if (num[i] > 1) {
            ans += 1LL*2*num[i]*(num[i]-1)*zero;
        }
    }
    ans += 1LL*(long long)(x[2*add].x+0.5)*zero;//x+y=0

    return ans;
}

int main () {
    scanf ("%d", &n);
    zero = cnt = 0;
    memset (num, 0, sizeof num);
    for (int i = 1; i <= n; i++) {
        scanf ("%d", &a[++cnt]);
        if (a[cnt] == 0) {
            zero++;
            cnt--;
            continue;
        }
        num[a[cnt]+add]++;
    }
    long long ans = solve ();
    printf ("%lld\n", ans);
    return 0;
}
/*
10
-50000 -50000 -50000 0 0 0 50000 50000 50000 1
4
0 0 -1 1
*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值