POJ 3071(Football) 直球概率DP+位运算

题目链接

http://poj.org/problem?id=3071

题意

2^n个球队,n轮比赛,每次比赛是按序号排好,第一打第二,第三打第四,如此。输了直接淘汰,给出任意两支队比赛获胜概率,问最可能夺冠的队伍。

思路

概率DP,dp[i][j]代表第i只球队撑过了第j轮的概率,对所有dp[i][n]求个max即可。

转移方程也好说,是
d p [ i ] [ j ] = d p [ i ] [ j − 1 ] ∗ ∑ k 与 j 可 以 对 决 ( d p [ k ] [ j − 1 ] ∗ p [ i ] [ k ] ) dp[i][j]=dp[i][j-1]*\sum_{k与j可以对决}(dp[k][j-1]*p[i][k]) dp[i][j]=dp[i][j1]kj(dp[k][j1]p[i][k])
通俗易懂,就是所有第i轮可以打架的队活下来的概率*打赢的概率求和,再乘上上一轮活下来的概率,就是活到这一轮的概率。

问题在于如何判断第i轮可以对决

这题n到7,所以可以暴力,用一个二维数组存第几轮打,n从1到7枚举,每次按pow(2,i)的“粒度”划分整个球队区间(比如i为3,那么就1-8一组,9-16一组划分),每个划分内部两层枚举,让没打过的在这轮打就可以了。复杂度O(能过) (睿智POJ一直T,后来才知道是关流用不了,fine)

另外一个更强的做法是位运算,球队按0–pow(2,n)-1编号,可以发现两支队在i轮能打时,他们的二进制下右起第i位互补,之后左边全部相等。真的是 我看不懂,但我大受震撼.jpg

(((j-1)>>(i-1))^1) == ((k-1)>>(i-1))//这里我是从1开始标号,所以j和k减一了
教训/收获

位运算找规律最好从0开始编号

代码
#include<cstdio>
#include<iostream>
#include<iomanip>
#include<map>
#include<string>
#include<queue>
#include<stack>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<cstdlib> 
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define endl "\n"
//#define int long long
//#define double long double
using namespace std;
	typedef long long ll;
	const int maxn=505;
	const int inf=0x3f3f3f3f;
	int n,m,k;
    double p[maxn][maxn];
    double dp[maxn][20];
	signed main(){
		#ifndef ONLINE_JUDGE
		    freopen("IO\\in.txt","r",stdin);
		    freopen("IO\\out.txt","w",stdout);
        #endif
		int tn=1;
        m=pow(2,7);
        while(scanf("%d",&n)){
            memset(dp,0,sizeof dp);
            if(n==-1)   break;
            m=pow(2,n);
            for(int i=1;i<=m;i++)
                for(int j=1;j<=m;j++)
                    scanf("%lf",&p[i][j]);
            for(int i=1;i<=m;i++)   dp[i][0]=1;
            for(int i=1;i<=n;i++){
                for(int j=1;j<=m;j++){
                    for(int k=1;k<=m;k++){
                        //if(mp[j][k]==i)
                        if((((j-1)>>(i-1))^1) == ((k-1)>>(i-1)))
                            dp[j][i]+=dp[k][i-1]*p[j][k];
                    }
                    dp[j][i]*=dp[j][i-1];
                }
            }
            double ma=0;
            int winner=0;
            for(int i=1;i<=m;i++)
                if(dp[i][n]>ma){
                    ma=dp[i][n];
                    winner=i;
                }
            printf("%d\n",winner);
        }
        
	} 
						
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值