木棍(概率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

posted @ 2022-09-27 17:13  f2021yjm  阅读(61)  评论(0编辑  收藏  举报