木棍(概率dp)
题目描述
n个长度不一的小木棍,这些木棍的长度都是正整数。
现规定一个长度k,让你闭着眼睛从n个中随便拿出两个,如果两个木棍的长度总和小于等于k,则你就胜利。
求你胜出的概率。
输入
输入包含两行。
第一行为两个整数n和k
第二行包含n个整数,分别为n个木棍的长度。
输出
输出包含一个实数,小明胜出的概率,保留两位小数。
样例输入
4 5
1 2 3 4
样例输出
0.67
数据范围
n,k∈[1,100000]
方法一 O(n×logn)(较暴力)
枚举每一根木棍,二分求出满足题意的组合的个数
代 码
#include <bits/stdc++.h>
using namespace std;
#define per(i,a,b) for(int i(a);i<=b;++i)
const int N=100010;
int a[N];
int main()
{
int n,k,l,r,mid;
cin>>n>>k;
long long tmp=(1ll*n*(n-1)),ans=0;//一共tm种情况
per(i,1,n)
{
scanf("%d",a+i);
}
sort(a+1,a+n+1);//排序
a[n+1]=N;//防止越界
per(i,1,n)
{
l=0,r=n+1;//二分找符合条件的数的个数
while(l+1<r)
{
mid=(l+r)>>1;
if(a[i]+a[mid]<=k)
{
l=mid;//l为能和当前的匹配的数目
}
else
{
r=mid;
}
}
if(l>=i) --l;//(如果l>i,说明i被算了两遍,减去)
ans+=l;
}
printf("%.2lf\n",(double)ans/tmp);
return 0;
}
方法二 O(k)
记录每个数的出现次数,从两边开始枚举选取的数
利用前缀和,组合原理,统计答案
代码
#include <bits/stdc++.h>
#define per(i,a,b) for(int i(a);i<=b;++i)
using namespace std;
const int N=100010;
int a[N],num[N];
int main()
{
int n,k,x;
cin>>n>>k;
per(i,1,n)
{
scanf("%d",&x);
if(x<k) num[x]++;//超过范围的直接丢掉
}
int i=1,j=k-1;//从范围两边开始,向中间枚举
double s=0,ans=0;//s为前缀和
while(j>i)
{
s+=num[i],ans+=num[j]*s;//计算每一个长度对答案的贡献
i++,j--;
}
if(i==j)
{
s+=num[i];
}
ans+=(s*(s-1))/2.0;
ans/=((long long)n*(n-1)/2.0);
printf("%.2lf\n",ans);
return 0;
}
参考文章:
https://blog.csdn.net/weixin_43944486/article/details/102448378
https://www.cnblogs.com/LJA001162/p/13124905.html