HDU 4609 3-idiots
原题链接
题意:给定n个线段长度,问任意选3个组成三角形的概率。
n<=1e5
假如我们能求出任意两个的长度,就可以选出第3条边了。如我们求出siz[i]表示任意两个不同的线段的和等于i的个数,那么枚举最长的一条边a[i],那么任意两条比a[i]+1大的都行ans+=siz[a[i]+1]+...+siz[mx],其次要减去选了a[i]自己的可能就是n-1,其次就是选了一大一小(n-i)*(i-1),都比a[i]大(n-i)*(n-i-1)/2。
下面考虑如何求siz[i],这就是经典的fft问题。构造多项式\(A(x)=num[0]+num[1]*x^1+...+num[n]*x^n\),\(B(x)=num[0]+num[1]*x^1+...+num[n]*x^n\),那么C(x)的i+j的系数就是独立选两条的和,接着num[a[i]+a[i]]--,减去自己选了两次,num[i]/=2,去掉选了两次的情况,即选了a[i]与a[j],又选了a[j]与a[i]。
时间复杂度:\(O(nlogn)\)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=2e6+10;
const double pi=acos(-1);
struct Complex{
double x,y;
friend Complex operator+(Complex x1,Complex y1)
{
return {x1.x+y1.x,x1.y+y1.y};
}
friend Complex operator-(Complex x1,Complex y1)
{
return {x1.x-y1.x,x1.y-y1.y};
}
friend Complex operator*(Complex x1,Complex y1)
{
return {x1.x*y1.x-x1.y*y1.y,x1.x*y1.y+x1.y*y1.x};
}
}af[maxn],bf[maxn];
int rev[maxn];
void init(int n)
{
rev[0]=0;
for(int i=0;i<n;i++)
{
rev[i]=rev[i>>1]>>1;
if(i&1) rev[i]|=n>>1;
}
}
void fft(Complex a[],int n,int inv)
{
for(int i=0;i<n;i++)
if(i<rev[i]) swap(a[i],a[rev[i]]);
for(int mid=1;mid<n;mid<<=1)
{
Complex it={cos(pi/mid),inv*sin(pi/mid)};
for(int i=0;i<n;i+=mid*2)
{
Complex w={1,0};
for(int j=0;j<mid;j++)
{
auto x=a[i+j],y=w*a[i+j+mid];
a[i+j]=x+y;a[i+j+mid]=x-y;
w=w*it;
}
}
}
}
ll a[maxn],siz[maxn];
int main()
{
int t;cin>>t;
while(t--)
{
int n;cin>>n;
memset(siz,0,sizeof(siz));
for(int i=1;i<=n;i++)
cin>>a[i],siz[a[i]]++;
sort(a+1,a+1+n);
int cnt=1;
while(cnt<2*a[n]+1) cnt*=2;
af[0]=bf[0]={0,0};
for(int i=1;i<cnt;i++)
af[i]=bf[i]={1.0*siz[i],0};
init(cnt);
fft(af,cnt,1);fft(bf,cnt,1);
for(int i=0;i<cnt;i++)
af[i]=af[i]*bf[i];
fft(af,cnt,-1);
for(int i=0;i<cnt;i++)
siz[i]=af[i].x/cnt+0.5; //O(nlogn)
for(int i=1;i<=n;i++)
siz[2*a[i]]--; //减去自己取两次
for(int i=0;i<cnt;i++) //siz[i,j] 与 siz[j,i]重复计算
siz[i]/=2;
ll ans=0;int mx=2*a[n];
for(int i=1;i<=mx;i++)
siz[i]=siz[i]+siz[i-1]; //求前缀和
for(int i=1;i<=n;i++)
{
ans+=siz[mx]-siz[a[i]]; //和比a[i]大的
ans-=1ll*(n-i)*(n-i-1)/2; //每个都比a[i]大
ans-=n-1; //和a[i]重复
ans-=1ll*(n-i)*(i-1); //一大一小
}
ll ans1=1ll*n*(n-1)*(n-2)/6;
printf("%.7f\n",1.0*ans/ans1);
}
return 0;
}

浙公网安备 33010602011771号