BZOJ 3513 [MUTC2013]idiots

题目大意

给定 n n 个长度分别为 ai 的木棒,问随机选择 3 3 根木棒能够拼成三角形的概率。

数据范围

对于 100% 的数据,最多100组数据,且满足 1n,ai105 1 ⩽ n , a i ⩽ 10 5

题解

拼成三角形的充要条件就是满足三角不等式。

三角不等式,即在三角形中两边之和大于第三边。

考虑到能拼成三角形的条件有些苛刻,这里先计算不能拼成三角形的概率,之后用总方案去减。
总方案为:从 n n 根木棒中选取 3 根木棒的方案数量为 C3n=n(n1)(n2)3×2×1 C n 3 = n ( n − 1 ) ( n − 2 ) 3 × 2 × 1

而不能拼成三角形即两边之和小于等于第三边。
ti t i 为长度为 i i 的木棒的数量,fi 为两根木棒的长度和为 i i 的方案数。
直接暴力计算的时候,直接把两种长度和为 i 的木棍数量乘起来就行了。
fi=j=1i1tj×tij f i = ∑ j = 1 i − 1 t j × t i − j
如果你和我说计算 f f 数组是 O(n2) 的,会超时,那你的FFT白学了。可以发现这就是卷积的形式。

下面考虑重复部分。哪些地方会有重复计算?举个例子。
两根长度为 1 1 的,三根长度为 2 的,则 t[12]={2,3} t [ 1 ⋯ 2 ] = { 2 , 3 } f[14]={0,4,12,9} f [ 1 ⋯ 4 ] = { 0 , 4 , 12 , 9 }
我们发现,当 i i 为偶数时,组成 fi 的一部分为 t2i t i 2 。这里面算上了选同一根木棒两次的情况,因此要减去 ti t i
修正后 f[14]={0,2,12,6} f [ 1 ⋯ 4 ] = { 0 , 2 , 12 , 6 }
又发现,选择木棒 a a 和木棒 b 与选择木棒 b b 和木棒 a 是等价的,因此需要除以 2 2
修正后 f[14]={0,1,6,3}

下面考虑计算出 f f 数组后如何统计答案。
加入第三根木棒,当第三根木棒长度为 k 时,所有长度和小于等于 k k 的另外两根木棒组合在一起都是非法的。即所有fi(1ik) 都不能组成三角形。对不能组成三角形的答案的贡献为 tk×i=1kfi t k × ∑ i = 1 k f i ,其中 i=1kfi ∑ i = 1 k f i 可以递推 O(n) O ( n ) 计算。

最后用总数减去非法数的差除以总数即得合法概率。

代码

f f 数组记得开 long long,总数记得开 long long l o n g   l o n g ,非法数记得开 long long l o n g   l o n g
重要的事情说三遍。
没开 long long l o n g   l o n g 卡了我 3h 3 h 。平均一个小时找出一个地方没开 long long l o n g   l o n g 我能怎么办。

#include<bits/stdc++.h>
using namespace std;
const int maxn=300010;
const double pi=acos(-1.0);
struct comp{
    double x,y;
    comp(double xx=0,double yy=0):x(xx),y(yy) {}
    friend comp operator+(const comp &x,const comp &y) {return comp(x.x+y.x,x.y+y.y);}
    friend comp operator-(const comp &x,const comp &y) {return comp(x.x-y.x,x.y-y.y);}
    friend comp operator*(const comp &a,const comp &b) {return comp(a.x*b.x-a.y*b.y,a.x*b.y+b.x*a.y);}
}a[maxn];
int limit=1,r[maxn];
void fft(comp *t,int ty=1){
    for(int i=0;i<limit;i++)
        if(i<r[i])
            swap(t[i],t[r[i]]);
    for(int mid=1;mid<limit;mid<<=1){
        comp wn(cos(pi/mid),ty*sin(pi/mid));
        for(int j=0,R=(mid<<1);j<limit;j+=R){
            comp w(1,0);
            for(int k=0;k<mid;k++,w=w*wn){
                comp x=t[j+k],y=w*t[j+k+mid];
                t[j+k]=x+y;
                t[j+k+mid]=x-y;
            }
        }
    }
}
#define ifft(a) fft(a,-1)
int t[maxn],T,n,mx;
long long f[maxn];//1. 记得开long long
int main(void){
    scanf("%d",&T);
    while(T--){
        memset(a,0,sizeof a);
        memset(r,0,sizeof r);
        memset(t,0,sizeof t);
        limit=1;mx=0;
        scanf("%d",&n);
        for(int i=1,x;i<=n;i++){
            scanf("%d",&x);
            mx=max(mx,x);
            ++t[x];
        }
        for(int i=1;i<=mx;i++)//减少精度误差
            a[i]=comp(t[i],0);
        int ln=mx<<1,l=0;//最长长度小于最长的木棒的两倍 
        while(limit<=ln)
            limit<<=1,++l;
        for(int i=1;i<limit;i++)
            r[i]=((r[i>>1]>>1)|((i&1)<<(l-1)));
        fft(a);
        for(int i=0;i<limit;i++)
            a[i]=a[i]*a[i];
        ifft(a);
        //2. 记得开long long
        long long tot=(long long)n*(n-1)*(n-2)/6,ans=0,sum=0;
        for(int i=1;i<=mx;i++){
            f[i]=floor(a[i].x/limit+0.5);//减少精度误差
            if(i%2==0)//去掉重复部分
                f[i]-=t[i>>1];
            f[i]>>=1;
        }
        for(int i=1;i<=mx;i++)
            f[i]+=f[i-1];//前缀和
        for(int i=1;i<=mx;i++)
            ans+=(long long)t[i]*f[i];//3. 记得开long long
        printf("%.7lf\n",1-(double)ans/tot);
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值