题目链接
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][j−1]∗k与j可以对决∑(dp[k][j−1]∗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);
}
}