GYM 100285 E. The Emperor’s plan(组合数学+dp)

202 篇文章 1 订阅
190 篇文章 1 订阅

Description
现在有n个参议员,其中有k个人是间谍,为了谋取自己的利益,现在每个间谍在每个晚上都会干掉不是间谍的参议员,一个间谍一天晚上只会干掉一个参议员,且间谍在晚上不会干掉自己人,在白天的时候,为了排除间谍,参议员们要排挤掉一部分人使得剩下的不是间谍的参议员的人数期望值最大,白天排挤一部分人出去的时候是随机的 (相当于白天间谍互相不认识) ,只是数量决定于排挤之后剩余的非间谍参议员的人数期望,即排挤出去的人数可以使得剩余的非间谍参议员的人数期望最大,这样子持续下去,多天之后只剩下全是间谍的参议员或者全不是间谍的参议员之后结束,问最终剩下的不是间谍的参议员的人数的期望值
Input
两个整数n和k分别表示参议员人数和间谍人数
Output
输出最终剩下的不是间谍的参议员的人数的期望值,小数点后保留十位
Sample Input
4 1
Sample Output
0.6666666667
Solution
用dp[x][y]表示有x个不是间谍的参议员和y个间谍时最终剩下的不是间谍的参议员的人数的期望值,显然当x<=y时,dp[x][y]=0,当y=0时,dp[x][y]=x,最终的我们需要的结果时dp[n-k][k]。其他时间,由于进入晚上间谍会杀人,所以一晚过后不是间谍的参议员还剩x-y个,间谍仍未y个,假设白天排挤出去i人,其中排挤出的间谍为j人,那么对于i从1到x-1分别计算出期望值然后取最大值递归计算即可,其中当排挤人时需要用到组合数以及除法原理,即从x个参议员中选取i人,从y个间谍中选取j人的概率为p=C(x,i)*C(y,j)/C(x+y,i+j),此时直接用除法显然不能满足精度,故需要用log+exp来计算,即p=exp(log(C(x,i))+log(C(y,j))-log(C(x+y,i+j)))=exp(log x!-log i!-log (x-i)!+log y!-log j!-log (y-j)!-log(x+y)!+log(i+j)!+log(x+y-i-j)!),而log n!可以用O(n)的时间预处理得到,这样一来既可以得到高精度的概率值了
Code

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
double lg[222];
bool vis[222][222];
double dp[222][222];
int n,k;
double get_c(int x,int y)
{
    return lg[x]-lg[y]-lg[x-y];
}
double get_p(int x,int y,int i,int j)//求概率值C(x,i)*C(y,j)/C(x+y,i+j) 
{
    return exp(get_c(x,i)+get_c(y,j)-get_c(x+y,i+j));
}
double goto_dp(int x,int y)
{
    if(x<=y)return 0;
    if(y==0)return x;
    if(vis[x][y])return dp[x][y];//该值已经计算过则直接返回即可 
    vis[x][y]=true;
    double ans=0;
    x-=y;//经过一晚上之后有y名参议员被杀 
    int num=x+y;
    for(int i=1;i<num;i++)//枚举白天被排挤出的人数 
    {
        double sum=0;
        int temp=min(i,y);
        for(int j=0;j<=temp;j++)//枚举间谍被排挤出的人数 
            sum+=goto_dp(x-(i-j),y-j)*get_p(x,y,i-j,j);
        ans=max(ans,sum);//更新不同排挤出人数下的最大期望值 
    }
    return dp[x+y][y]=ans;//注意记录答案 
}
int main()
{
    lg[0]=0;
    for(int i=1;i<222;i++)//预处理得到log n!的值 
        lg[i]=lg[i-1]+log(1.0*i);
    memset(dp,0,sizeof(dp));//初始化 
    while(~scanf("%d%d",&n,&k))
        printf("%.10lf\n",goto_dp(n-k,k));
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值