传送门:http://acm.hdu.edu.cn/showproblem.php?pid=4609
3-idiots
Time Limit: 10000/5000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 2918 Accepted Submission(s): 1005
Problem Description
King OMeGa catched three men who had been streaking in the street. Looking as idiots though, the three men insisted that it was a kind of performance art, and begged the king to free them. Out of hatred to the real idiots, the king wanted to check if they were lying. The three men were sent to the king's forest, and each of them was asked to pick a branch one after another. If the three branches they bring back can form a triangle, their math ability would save them. Otherwise, they would be sent into jail.
However, the three men were exactly idiots, and what they would do is only to pick the branches randomly. Certainly, they couldn't pick the same branch - but the one with the same length as another is available. Given the lengths of all branches in the forest, determine the probability that they would be saved.
However, the three men were exactly idiots, and what they would do is only to pick the branches randomly. Certainly, they couldn't pick the same branch - but the one with the same length as another is available. Given the lengths of all branches in the forest, determine the probability that they would be saved.
Input
An integer T(T≤100) will exist in the first line of input, indicating the number of test cases.
Each test case begins with the number of branches N(3≤N≤10 5).
The following line contains N integers a_i (1≤a_i≤10 5), which denotes the length of each branch, respectively.
Each test case begins with the number of branches N(3≤N≤10 5).
The following line contains N integers a_i (1≤a_i≤10 5), which denotes the length of each branch, respectively.
Output
Output the probability that their branches can form a triangle, in accuracy of 7 decimal places.
Sample Input
2 4 1 3 3 4 4 2 3 3 4
Sample Output
0.5000000 1.0000000
Source
题目意思:给n段长度,问能组成多少三角形。
预处理一边长度为 L 的个数。cnt[L]。
通过FFT预处理两边相加长度为 L 的个数。num[L]=sigma(cnt[L])。
去重,即去掉自己加自己:num[L+L]--。
不考虑选择的先后顺序,num[L]/=2。
那么现在已经知道了长度L(两边和)的个数了。
枚举原始数据即一边长为a[i],那么就要找多少两边之和大于a[i]。
为了避免重复,我们选择两边之和大于a[i]且两边自身都小于a[i]的两边。
为什么呢?
cnt=0;
首先,三角形中最小的两边之和都大于第三边了,就一定是三角形了,不需要两边只差小于第三边的验证。
那么,对于a[i]怎么统计呢?我们需要知道两边和比a[i]大的总数,num[a[i]+1]+num[a[i]+2]+num[a[i]+3]+...+num[maxL] (a[i]已经小到大排序)
因此可以用个前缀和维护,cnt+=sum[maxL]-sum[a[i]]
其次,假设对a[i]来说,其余两边为 a[j]和a[k]的话,后面遇到a[j]和a[k]又会重复计算,因此我们给定一个标准即可避免重复计算。
cnt-=i*(n-1-i)//一大一小
cnt-=(n-1)//一边是自己,一边是其余
cnt-=(n-i-1)*(n-i-2)//两边都大
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define rt return
#define db double
#define op operator
#define LL long long
using namespace std;
const int maxn = 400001;
const db PI = acos(-1.0);
const db eps = 1e-8;
inline int sig(db x){rt (x>eps)-(x<-eps); }
int n;
int a[maxn];
LL num[maxn],sum[maxn];//全部100000,则200000的情况有100000*100000种,求卷积需要乘
struct complex{
db x,y;
complex(db _x=0,db _y=0):x(_x),y(_y){}
complex op + (complex a){rt complex(x+a.x,y+a.y); }
complex op - (complex a){rt complex(x-a.x,y-a.y); }
complex op * (complex a){rt complex(x*a.x-y*a.y,x*a.y+y*a.x); }
};
void change(complex p[],int len){
for(int i=1,j=len/2;i<len-1;i++){
if(i<j)swap(p[i],p[j]);
int k=len/2;
while(j>=k){
j-=k;
k/=2;
}
if(j<k)j+=k;
}
}
void fft(complex p[],int len,int on){
change(p,len);
for(int h=2;h<=len;h*=2){
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=p[k];
complex t=w*p[k+h/2];
p[k]=u+t;
p[k+h/2]=u-t;
w=w*wn;
}
}
}
if(on==-1){
for(int i=0;i<len;i++){
p[i].x/=len;
}
}
}
complex p[maxn];
void juanji(){
int len1=a[n-1]+1;
int len=1;
while(len<2*len1)len<<=1;
for(int i=0;i<len1;i++)
p[i]=complex(num[i],0);
for(int i=len1;i<len;i++)
p[i]=complex(0,0);
fft(p,len,1);
for(int i=0;i<len;i++)
p[i]=p[i]*p[i];
fft(p,len,-1);
for(int i=0;i<len;i++)
num[i]=(LL)(p[i].x+0.5);//0.999999+0.5
}
int main(){
int T;scanf("%d",&T);
while(T--){
memset(num,0,sizeof num);
memset(sum,0,sizeof sum);
scanf("%d",&n);
for(int i=0;i<n;i++){
scanf("%d",&a[i]);
num[a[i]]++;
}
sort(a,a+n);
juanji();
int len=a[n-1]*2;
for(int i=0;i<n;i++)
num[a[i]+a[i]]--;
for(int i=1;i<=len;i++)
num[i]/=2;
num[0]=0;
for(int i=1;i<=len;i++)
sum[i]=sum[i-1]+num[i];
LL cnt=0;
for(int i=0;i<n;i++){
cnt+=sum[len]-sum[a[i]];//>a[i]长度的和的总数;
cnt-=(LL)(n-1-i)*i;//比a[i]一大一小的
cnt-=(LL)(n-1);//取一个本身,取一个其他的
cnt-=(LL)(n-1-i)*(n-2-i)/2;//2个比a[i]大的
}
LL tot=(LL)n*(n-1)*(n-2)/6;
db ans=1.0*cnt/tot;
printf("%.7lf\n",ans);
}
rt 0;
}
//3 4 5 6 7
//我们只取5的时候的情况,即找两个都小于5的和。
//怎么保证两边之差小于第三边??
//因为取得是两个都小于a[i]的数,即使 不减都小于a[i],减了更小于a[i]了
//1 2 3
//对于1,其中2 3 都大于1,这种情况被剪掉了
//对于2,其中1 3 一大一小,被剪掉
//对于3,其中1+2 不大于3,不在cnt中