HDU 4609 3-idiots (FFT)

题意:

给你n (n <= 10^5)个边长ai (ai <= 10^5),问随机取出三条边可以构成三角形的概率是多少。


解题思路:

首先要学会FFT,然后就很好做了。cnt[i]表示边长为i的有多少条,如果要算出所有两条边长的和分别是多少,普通算法是O(n^2),利用FFT卷积可以O(nlogn)得到所有的,然后去重一下O(n)处理总数即可。具体见代码


 

/* **********************************************
Author      : JayYe
Created Time: 2013-9-26 20:32:45
File Name   : JayYe.cpp
*********************************************** */

#include <stdio.h>
#include <math.h>
#include <string.h>
#include <algorithm>
using namespace std;
typedef long long ll;
const double pi = acos(-1.0);
const int maxn = 100000 + 5;
const double eps = 1e-6;

struct Complex {
    double a, b;
    Complex() {}
    Complex(double a, double b) : a(a), b(b) {}

    Complex operator + (const Complex& t) const {
        return Complex(a + t.a, b + t.b);
    }

    Complex operator - (const Complex& t) const {
        return Complex(a - t.a, b - t.b);
    }
    Complex operator * (const Complex& t) const {
        return Complex(a*t.a - b*t.b, a*t.b + b*t.a);
    }
};

void brc(Complex *a, int n) {
    int i, j, k;
    for(i = 1, j = n>>1;i < n - 1; i++) {
        if(i < j)   swap(a[i], a[j]);
        k = n>>1;
        while(j >= k) {
            j -= k;
            k >>= 1;
        }
        if(j < k)
            j += k;
    }
}

void FFT(Complex *a, int n, int on) {
    int h, i, j, k, p;
    double r;
    Complex u, t;
    brc(a, n);
    for(h = 2;h <= n; h <<= 1) {
        r = on*2.0*pi / h;
        Complex wn(cos(r), sin(r));
        p = h>>1;
        for(j = 0;j < n;j += h) {
            Complex w(1, 0);
            for(k = j;k < j + p; k++) {
                u = a[k];
                t = w*a[k + p];
                a[k] = u + t;
                a[k+p] = u - t;
                w = w*wn;
            }
        }
    }
    if(on == -1) {
        for(i = 0;i < n; i++)
            a[i].a = a[i].a / n + eps;
    }
}

Complex x1[maxn<<2];
int sa[maxn];
ll cnt[maxn<<1];

void solve() {
    int n, mx = 0;
    memset(cnt, 0, sizeof(cnt));
    scanf("%d", &n);
    for(int i = 0;i < n; i++) {
        scanf("%d", &sa[i]);
        cnt[sa[i]] ++;
        mx = max(mx, sa[i]);
    }
    int N = 1, tmpn = 2*mx+1;
    while(N < tmpn) N <<= 1;
    for(int i = 0;i < N; i++)
        x1[i].a = x1[i].b = 0;
    for(int i = 0;i <= mx; i++)
        x1[i].a = cnt[i];
    FFT(x1, N, 1);
    for(int i = 0;i < N; i++)
        x1[i] = x1[i]*x1[i];
    FFT(x1, N, -1);
    mx <<= 1;
    for(int i = 0;i <= mx; i++)
        cnt[i] = (ll) (x1[i].a + eps);
    // FFT后得到的是cnt[i]表示任取两条边和为i的有多少种
    for(int i = 0;i < n; i++) {
        cnt[sa[i]*2]--;        // 减去自己边与自己边的和
    }
    // 由于是取出两条边,所以要除二
    for(int i = 0;i <= mx; i++)
        cnt[i] /= 2;
    for(int i = 1;i <= mx; i++)
        cnt[i] += cnt[i-1];     // 算出前缀和
    sort(sa, sa + n);
    ll ans = 0;
    for(int i = 0;i < n; i++) {
        ans += cnt[mx] - cnt[sa[i]];
        ans -= (ll)(n - i - 1)*(n - i - 2)/2 + n-1 + (ll)(n - i - 1)*i;
    }
    printf("%.7lf\n", (double)ans / ((ll)n*(n-1)*(n-2)/6));
}

int main() {
    int t;
    scanf("%d", &t);
    while(t--) {
        solve();
    }
    return 0;
} 


 

 

posted on 2013-09-27 19:19  新一  阅读(328)  评论(0编辑  收藏  举报

导航