BZOJ 3513 [MUTC2013]idiots

本文探讨了一个算法问题,即给定多个不同长度的木棒,如何计算随机选择三根木棒能拼成三角形的概率。通过分析三角不等式条件,采用FFT快速傅里叶变换进行高效计算,解决了复杂度问题,并提供了完整的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目大意

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

数据范围

对于 100% 的数据,最多100组数据,且满足 1n,ai1051⩽n,ai⩽105

题解

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

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

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

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

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

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

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

代码

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

#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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值