POJ 3071 Football

博客讲述了如何使用动态规划(dp)解决POJ 3071 Football问题,该问题涉及2^n个足球队通过比赛决出冠军的概率计算。博主详细解析了状态转移方程,并提供了两种不同的dp实现思路,包括一种简洁的方法和博主自己的复杂实现,强调了细节处理的重要性。
摘要由CSDN通过智能技术生成

题目大意:有2^n个足球队,编号依次为1,2,3……2^n,按照递增的顺序每两个相邻的队伍之间进行比赛,赢的队伍进入下一轮,然后再按照同样的规则进行比赛,共进行n轮比赛,最后剩下的一个队伍即为冠军,求最可能成为冠军的队伍。输入为2^n行2^n列,第i 行第 j 列的值p[i][j] 表示第i个队伍打败第j个队伍的概率。


解题思路:据大神说,这是个简单的dp题,于是我就一直推啊推,历经两个小时终于推出状态转移方程了,交一发wrong掉了,后来发现是一个小细节处理失误了,嘎嘎 貌似这是第一次独立做出dp题啊,HAPPY啊~~ 

我想的比较绕,先说一下大神的思路吧 ,简单易懂。

用dp[i][j]表示第i轮比赛第j个队伍获胜的概率,则dp[0][i]  = 1 , dp[i][j] +=  dp[i-1][j] * dp[i-1][k] * p[j][k]; k取决于i和j。最后找到dp[n][i]中最大值对应的队伍号即可。

假设有4支队伍,第一轮对决:0和1,2和3;第二轮:0和2或3, 1和2或3,2和1或0,3和1或0 ……(队伍号下标从0开始)

第一轮:

dp[1][0] = dp[0][0] * dp[0][1] * p[0][1];

dp[1][1] = dp[0][1] * dp[0][0] * p[1][0];

dp[1][2] = dp[0][2] * dp[0][3] * p[2][3];

dp[1][3] = dp[0][3] * dp[0][2] * p[3][2];


第二轮:

dp[2][0] = dp[1][0] * dp[1][2] * p[0][2] + dp[1][0] * dp[1][3] * p[0][3] 

dp[2][1] = dp[1][1] * dp[1][2] * p[1][2] + dp[1][1] * dp[1][3] * p[1][3]

dp[2][2] = dp[1][2] * dp[1][0] * p[2][0] + dp[1][2] * dp[1][1] * p[2][1]

dp[2][3] = dp[1][3] * dp[1][0] * p[3][0] + dp[1][3] * dp[1][1] * p[3][1]


这儿实现有个小技巧 ,具体看代码:

#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>

#define N 1024
double dp[10][N], p[N][N];
int main()
{
    int n, m;
    while(~scanf("%d", &m))
    {
        if(m==-1) break;
        n = 1 << m;
        for(int i = 0; i < n; i ++)
        {
            for(int j = 0; j < n; j ++)
            {
                scanf("%lf", &p[i][j]);
            }
        }
        for(int i = 0; i < n; i ++) dp[0][i] = 1;
        for(int i = 1; i <= m; i ++)
        {
            int temp = (1 << (i-1));
            for(int j = 0; j < n; j ++)
            {
                int s = j / temp;
                s ^= 1;
               // printf("s= %d\n", s);
                dp[i][j] = 0;
                for(int k = s * temp; k < s * temp + temp; k ++)
                {
                    dp[i][j] += dp[i-1][j] * dp[i-1][k] * p[j][k];
                }
            }
        }
        double ma = 0;
        int pos = 0;
        for(int i = 0; i < n; i ++)
        {
            if(ma < dp[m][i])
            {
                ma = dp[m][i];
                pos = i;
            }
            //printf("%.4lf\n", dp[m][i]);
        }
         printf("%d\n",pos+1);
    }
    return 0;
}


我的想法其实和这个差不多的,不过实现起来比人家的蹩脚多了,我是用dp[i] 表示第i个队伍获胜的概率,每轮比赛后更新一下dp[i]的值,这里需要用到一个辅助数组tmp[],tmp[i] += dp[i] * dp[k] * p[i][k],dp[i] = tmp[i]; 具体代码实现如下(比较恶心,我都不知道自己是怎么想出来的):


#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>

#define N 1024
double dp[N], p[N][N], tmp[N];
int main()
{
    int n, m;
    while(~scanf("%d", &m))
    {
        if(m==-1) break;
        n = 1 << m;
        for(int i = 1; i <= n; i ++)
        {
            for(int j = 1; j <= n; j ++)
            {
                scanf("%lf", &p[i][j]);
            }
        }
        for(int i = 1;i <= n; i ++)
        {
            if(i & 1)
                dp[i] = p[i][i+1];
            else dp[i] = p[i][i-1];
        }

        for(int z = 1; z < m; z ++)
        {
            int  temp = 1 << z;
            for(int i = 1; i <= n; i+=(1<<z))
            {
                int s = i + temp;
                if((i % temp != 0) && ((i / temp) & 1) )
                    s = i - temp;
                for(int k = i; k < i + (1<<z); k++)
                {
                     tmp[k] = 0;
                    for(int j = s; j< s+(1<<z); j++)
                    {
                        tmp[k] += dp[k] * dp[j] * p[k][j];
                    }
                }

            }
            for(int k = 1; k <=n; k++)
            {
                dp[k] = tmp[k];
            }
        }
        double ma = 0;
        int pos = 0;
        for(int i = 1; i <= n; i ++)
            if(ma < dp[i])
            {
                ma = dp[i];
                pos = i;
            }
        printf("%d\n",pos);

    }
    return 0;
}



 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值