CF859D Third Month Insanity

洛咕

题意:有\(2^n\)个人要进行比赛,每次\(2i\)\(2i+1\)号人进行比赛.这一轮中赢的人进入下一轮.下一轮比赛的时候把进入这一轮的人按编号排好,仍然是像之前那样相邻的进行一次比赛.最后只剩下一个人.数据给出对于$ x,y\(,\)x\(打赢\)y\(的概率.第\)i$轮比赛会角逐出\(2^{n-i}\)个赢家.我们要在比赛开始前,猜每轮的赢家(一轮的赢家一定要是上一轮的赢家).第$ i $轮每猜中一个赢家就会得到\(2^{i-1}\)的得分.求最大的期望得分.\(n<=6\)

分析:不难发现整个比赛其实是一棵完全二叉树,树的第i层代表第i轮比赛,树上的每个节点都代表一个区间(这一轮的参赛人的编号).

所以我们考虑在树上dfs,设\(w[i][j]\)表示在以i号节点为根的树中(即在第i轮比赛中)j获胜的概率.设\(f[i][j]\)表示在以i号节点为根的树中(即在第i轮比赛中)j获胜的最大期望得分.

对于w数组的转移,我们只要从左子树和右子树中分别选一个节点,然后结合概率来算就好了.对于f数组的转移,一定要在w数组转移之后,同样是从左子树和右子树中分别选一个节点,然后算期望就好了.具体的转移方程直接看代码吧,很好理解.

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define ll long long
using namespace std;
inline int read(){
    int x=0,o=1;char ch=getchar();
    while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
    if(ch=='-')o=-1,ch=getchar();
    while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
    return x*o;
}
const int N=105;
double p[N][N],w[N*10][N],f[N*10][N];
inline void dfs(int root,int l,int r,int val){
	if(l==r){//叶子节点,即最后一轮比赛
		w[root][l]=1;
		f[root][l]=val;
		return;
	}
	int mid=(l+r)>>1;
	dfs(root<<1,l,mid,val/2);
	dfs(root<<1|1,mid+1,r,val/2);
	for(int i=l;i<=mid;++i)
		for(int j=mid+1;j<=r;++j){
			w[root][i]+=w[root<<1][i]*w[root<<1|1][j]*p[i][j];
		}
	for(int i=mid+1;i<=r;++i)
		for(int j=l;j<=mid;++j){
			w[root][i]+=w[root<<1][j]*w[root<<1|1][i]*p[i][j];
		}
	for(int i=l;i<=mid;++i)
		for(int j=mid+1;j<=r;++j){
			f[root][i]=max(f[root][i],w[root][i]*val+f[root<<1][i]+f[root<<1|1][j]);
		}
	for(int i=mid+1;i<=r;++i)
		for(int j=l;j<=mid;++j){
			f[root][i]=max(f[root][i],w[root][i]*val+f[root<<1][j]+f[root<<1|1][i]);
		}
}
int main(){
	int n=read();n=1<<n;
	for(int i=1;i<=n;++i){
		for(int j=1;j<=n;++j){
			int x=read();
			p[i][j]=0.01*x;//把整数变为概率
		}
	}
	dfs(1,1,n,n/2);double ans=0.0;
	for(int i=1;i<=n;++i)ans=max(ans,f[1][i]);
	printf("%.10lf\n",ans);
    return 0;
}

posted on 2019-09-17 15:50  PPXppx  阅读(192)  评论(0编辑  收藏  举报