2 4 1 3 3 4 4 2 3 3 4
0.5000000 1.0000000
学会了FFT。
这题就很容易了。
其实题目是给了n条线段。问随机取三个,可以组成三角形的概率。
其实就是要求n条线段,选3条组成三角形的选法有多少种。
首先题目给了a数组,
如样例一:
4
1 3 3 4
把这个数组转化成num数组,num[i]表示长度为i的有num[i]条。
样例一就是
num = {0 1 0 2 1}
代表长度0的有0根,长度为1的有1根,长度为2的有0根,长度为3的有两根,长度为4的有1根。
使用FFT解决的问题就是num数组和num数组卷积。
num数组和num数组卷积的解决,其实就是从{1 3 3 4}取一个数,从{1 3 3 4}再取一个数,他们的和每个值各有多少个
例如{0 1 0 2 1}*{0 1 0 2 1} 卷积的结果应该是{0 0 1 0 4 2 4 4 1 }
长度为n的数组和长度为m的数组卷积,结果是长度为n+m-1的数组。
{0 1 0 2 1}*{0 1 0 2 1} 卷积的结果应该是{0 0 1 0 4 2 4 4 1 }。
这个结果的意义如下:
从{1 3 3 4}取一个数,从{1 3 3 4}再取一个数
取两个数和为 2 的取法是一种:1+1
和为 4 的取法有四种:1+3, 1+3 ,3+1 ,3+1
和为 5 的取法有两种:1+4 ,4+1;
和为 6的取法有四种:3+3,3+3,3+3,3+3,3+3
和为 7 的取法有四种: 3+4,3+4,4+3,4+3
和为 8 的取法有 一种:4+4
利用FFT可以快速求取循环卷积,具体求解过程不解释了,就是DFT和FFT的基本理论了。
总之FFT就是快速求到了num和num卷积的结果。只要长度满足>=n+m+1.那么就可以用循环卷积得到线性卷积了。
弄完FFT得到一个num数组,这个数组的含义在上面解释过了。
while( len < 2*len1 )len <<= 1; for(int i = 0;i < len1;i++) x1[i] = complex(num[i],0); for(int i = len1;i < len;i++) x1[i] = complex(0,0); fft(x1,len,1); for(int i = 0;i < len;i++) x1[i] = x1[i]*x1[i]; fft(x1,len,-1); for(int i = 0;i < len;i++) num[i] = (long long)(x1[i].r+0.5);
这里代码中的num数组就是卷积后的结果,表示两两组合。
但是题目中本身和本身组合是不行的,所有把取同一个的组合的情况删掉。
//减掉取两个相同的组合
for(int i = 0;i < n;i++) num[a[i]+a[i]]--;
还有,这个问题求组合,所以第一个选t1,第二个选t2,和第一个选t2,第二个选t1,我们认为是一样的。
所有num数组整体除于2
//选择的无序,除以2
for(int i = 1;i <= len;i++) { num[i]/=2; }
然后对num数组求前缀和
sum[0] = 0; for(int i = 1;i <= len;i++) sum[i] = sum[i-1]+num[i];
之后就开始O(n)找可以形成三角形的组合了。
a数组从小到大排好序。
对于a[i]. 我们假设a[i]是形成的三角形中最长的。这样就是在其余中选择两个和>a[i],而且长度不能大于a[i]的。(注意这里所谓的大于小于,不是说长度的大于小于,其实是排好序以后的,位置关系,这样就可以不用管长度相等的情况,排在a[i]前的就是小于的,后面的就是大于的)。
根据前面求得的结果。
长度和大于a[i]的取两个的取法是sum[len]-sum[a[i]].
但是这里面有不符合的。
一个是包含了取一大一小的
cnt -= (long long)(n-1-i)*i;
一个是包含了取一个本身i,然后取其它的
cnt -= (n-1);
还有就是取两个都大于的了
cnt -= (long long)(n-1-i)*(n-i-2)/2;
这样把i从0~n-1累加,就答案了。
long long cnt = 0; for(int i = 0;i < n;i++) { cnt += sum[len]-sum[a[i]]; //减掉一个取大,一个取小的 cnt -= (long long)(n-1-i)*i; //减掉一个取本身,另外一个取其它 cnt -= (n-1); //减掉大于它的取两个的组合 cnt -= (long long)(n-1-i)*(n-i-2)/2; }
解释参考:http://www.cnblogs.com/kuangbin/archive/2013/07/24/3210565.html
#include <iostream> #include <stdio.h> #include <cmath> #include <algorithm> #include <string.h> #define LL long long #define scan(a) scanf("%d",&a) #define REP(i,a,b) for(int i=a;i<b;++i) #define mset(a,b) memset(a,b,sizeof a) //#define maxn 1e6+10 using namespace std; const double PI = acos(-1.0); const LL mod = 998244353; const int maxn = 5e5+10; struct Complex { double x,y; Complex(double _x = 0.0,double _y=0.0) { x=_x; y=_y; } Complex operator -(const Complex &b) const { return Complex(x-b.x,y-b.y); } Complex operator +(const Complex &b)const { return Complex(x+b.x,y+b.y); } Complex operator *(const Complex &b)const { return Complex(x*b.x-y*b.y,x*b.y+y*b.x); } }; void change(Complex y[],int len) { int i,j,k; for(int 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(Complex y[],int len,int on) { change(y,len); for(int h=2;h<=len;h<<=1) { Complex wn(cos(-on*2*PI/h),sin(-on*2*PI/h)); for(int j=0;j<len;j+=h) { Complex w(1,0); for(int k=j;k<j+h/2;k++) { Complex u=y[k]; Complex 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; } Complex x1[maxn],x2[maxn]; int a[maxn]; LL num[maxn]; int main() { int t; cin>>t; while(t--) { int n; scan(n); mset(num,0); REP(i,0,n) {scan(a[i]);num[a[i]]++;} sort(a,a+n); int len=1,man=a[n-1]+1; while(len<man*2) len<<=1; REP(i,0,man) { x1[i]=Complex(num[i],0); //x2[i]=Complex(num[i],0); } REP(i,man,len) { x1[i]=Complex(0,0); //x2[i]=Complex(0,0); } fft(x1,len,1); //fft(x2,len,1); REP(i,0,len) x1[i]=x1[i]*x1[i]; fft(x1,len,-1); mset(num,0); REP(i,0,len) { num[i]=(LL)(x1[i].x+0.5); } len=2*a[n-1]; REP(i,0,n) num[a[i]+a[i]]--; REP(i,1,len+1) num[i]/=2; REP(i,2,len+1) num[i]+=num[i-1]; LL sum=0; REP(i,1,n+1) { sum += num[len]-num[a[i-1]]; sum -=n-1; sum -=(LL)(i-1)*(n-i); sum -=(LL)(n-i)*(n-i-1)/2; } double nn=n; nn=nn*(nn-1)*(nn-2)/6; double ans=sum/nn; printf("%.7lf\n",ans); } }