poj_3071 Football(概率dp)
直接上状态转移方程:
记dp[i][j]为第i轮比赛,第j个队伍获胜的概率。
那么初始状态下,dp[0][j]=1;//也就是第0轮比赛全都获胜
d[i][j]=sum(d[i-1][j]*d[i-1][k]*win[j][k])//也就是找到所有可能与j队在第i轮对决的k队,那么i队战胜k队的概率为 :第i-1轮第j队出线的概率*第i-1轮第k支队伍出现的概率*第i轮j队打败k队的概率。将所有的k的可能取值都遍历一遍求概率和,所得结果就是d[i][j]。
其中,如何找到所有可能的k队是一个难点。
一般想法是先进行分组:
例如共8支队伍
第一轮中分组为(1,2)(3,4)(5,6)(7,8)
第二轮中分组为((1|2),(3|4))((5|6),(7|8))
因此不妨先按组号进行划分:
第一轮第1组组员有1,2;第2组有3,4;第3组有5,6;第四组有7,8;
观察可发现 组号=(队号-1)>>1;
同样的在第二轮中 组号=(队号-1)>>2;
那么第n轮中 组号=(队号-1)>>n;
分完组只是第一步。
接下来我们知道,只有组号为相邻的两个数的队伍才可能比赛(而且这个相邻是有序的,必须满足(a,a+1)其中a为奇数)
这里应该可以按照奇偶性来判断,不过我没有尝试。而是用了另一个更简便的技巧:用异或运算来判断相邻。(异或完美的适配题目要求的这种相邻,具体见程序)
AC代码如下:
#include<iostream> #include<cstdio> #include<cstdlib> #include<cstring> using namespace std; double win[150][150]; double dp[10][150]; int main(void){ int n; while(scanf("%d",&n)==1&&n!=-1){ memset(dp,0,sizeof(dp)) ; int len=1<<n; for(int i=1;i<=len;i++){ for(int j=1;j<=len;j++) scanf("%lf",&win[i][j]); } for(int i=0;i<=len;i++){//初始化 dp[0][i]=1; } for(int i=1;i<=n;i++){ for(int j=1;j<=len;j++){ //自己想,怎么判断能比赛 for(int k=1;k<=len;k++) { if(((k-1)>>(i-1))==(((j-1)>>(i-1))^1)){//优先级 链接:https://blog.csdn.net/u013630349/article/details/47444939 //位移运算相当于在进行分组,异或运算则是判断相邻,而且这个相邻是正好满足题目条件的有规律的相邻 ,可以写几个数试一下。 dp[i][j]+=dp[i-1][j]*dp[i-1][k]*win[j][k]; } } } } double max=dp[n][1]; int ans=1; for(int i=1;i<=len;i++){ if(dp[n][i]>max){ max=dp[n][i]; ans=i; } } printf("%d\n",ans); } return 0; }