题意:
给你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; }