hdu4609 3-idiots FFT

     跟n条边,求任选三条边可以组成三角形的概率。求出n条边组成三角形的方案数,再除以C(n,3)就可以了,所以这题转化成n条边求可组成的三角形数。

首先用一个数组num[i]记录长度为i的边出现了几次,之后求num的卷积,即可得到n条边中任取两条边的和的长度各出现了多少次,拿第一组样例来说

a[]={1 3 3 4}转化成num={0,1,0,2,1},{0,1,0,2,1}*{0,1,0,2,1}得到{0,0,1,0,4,2,4,4,1},乘积即两边之和出现的次数,但是我们在选取的时候一条边不能用两次,而a+b和b+a只记一次,所以要对这个乘积处理一下,即num[a[i]+a[i]]--, num[i]/=2, 处理后的结果{0,0,0,0,2,1,1,2,0}就是C(n,2)得到的长度和出现的次数.之后枚举a[i],假设a[i]为三边的最大值,cnt=sum[max]-sum[a[i]]就是任选两边长度符合的种数,其中sum[i]为num[]的前缀和,而这个cnt中还有非法的情况:

cnt-=(ll)(n-i-1)*i;//b,c一个大于a[i],一个小于
cnt-=(ll)(n-1);//b,c中一个是a[i]
cnt-=(ll)(n-i-1)*(n-i-2)/2;//b,c都大于a[i]

最后累加所有的cnt就是最后的答案。num[]的卷积可以用快速傅里叶变换(FFT)以nlogn的复杂度完成,没做过fft的同学可以参考

http://blog.csdn.net/night_raven/article/details/20546435


#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;
typedef long long ll;
const double PI = acos(-1.0);
struct comp
{
    double r,i;
    comp(double rt=0,double it=0)
    {
        r=rt;
        i=it;
    }
    comp operator +(const comp& b)
    {
        return comp(r+b.r,i+b.i);
    }
    comp operator -(const comp &b)
    {
        return comp(r-b.r,i-b.i);
    }
    comp operator *(const comp &b)
    {
        return comp(r*b.r-i*b.i,r*b.i+i*b.r);
    }
};

void change(comp y[],int len)//二进制转置--雷德算法
{
    int i,j,k;
    for(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(comp y[],int len,int on)
/* on=1 DFT 把一个多项式的系数向量转化为点集表示;
on=-1,IDFT 把一个点集转化成多项式的系数向量*/
{
    change(y,len);
    for(int h = 2;h <= len;h <<= 1)
    {
        comp wn(cos(-on*2*PI/h),sin(-on*2*PI/h));
        for(int j = 0;j < len;j += h)
        {
            comp w(1,0);
            for(int k = j;k < j+h/2;k++)
            {
                comp u = y[k];
                comp 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].r /= len;
}

void conv(comp f[],int len)//求f的卷积
{
    fft(f,len,1);
    for (int i=0; i<len; i++)
    f[i]=f[i]*f[i];
    fft(f,len,-1);
}
int n,m;
const int maxn=404000;
comp x1[maxn];
int a[maxn];
ll num[maxn],sum[maxn];
int p,q,k,t;
int len1,len;
int main()
{
//    freopen("in.txt","r",stdin);
    int tt;
    scanf("%d",&tt);
    while(tt--)
    {
        memset(num,0,sizeof num);
        scanf("%d",&n);
        for (int i=0; i<n; i++)
        scanf("%d",&a[i]),num[a[i]]++;
        sort(a,a+n);
        len1=a[n-1]+1;
        len=1;
        while (len<len1*2) len<<=1;
        for (int i=0; i<len1; i++)
        x1[i]=comp(num[i],0);
        for (int i=len1; i<len; i++)
        x1[i]=comp(0,0);

        conv(x1,len);
        for (int i=0; i<len; i++)
        num[i]=(ll)(x1[i].r+0.5);//四舍五入

        len=2*a[n-1];

        //去重
        for (int i=0; i<n; i++)
        num[a[i]+a[i]]--;//相同的组合减点一个
        for (int i=1; i<=len; i++)
        num[i]>>=1;//选择有序,除以2



        memset(sum,0,sizeof sum);
        for (int i=1; i<=len; i++)
        sum[i]=sum[i-1]+num[i];

        ll cnt=0;
        ll tot=(ll)n*(n-1)*(n-2)/6; //C(n,3);
        ll ans=0;
        for (int i=0; i<n; i++)
        {
            //假设a[i]为三角形的最长边,枚举

            cnt=sum[len]-sum[a[i]];//b+c>a[i]的取法

            cnt-=(ll)(n-i-1)*i;//b,c一个大于a[i],一个小于
            cnt-=(ll)(n-1);//b,c中一个是a[i]
            cnt-=(ll)(n-i-1)*(n-i-2)/2;//b,c都大于a[i]
            ans+=cnt;
        }
        double out=(double)ans/(double)tot;
        printf("%.7lf\n",out);

    }
    return 0;
}





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值