poj-3071

来源:互联网 发布:ubuntu配置keras环境 编辑:程序博客网 时间:2024/05/22 13:47
//528K  94MS    G++ #include <cstdio>#include <cstring>#include <cmath>const int MAX = 130;double P[MAX][MAX];double DP[MAX][9];int N;int teamNum;void getVSId(int curTeamId, int winTime, int & beginVSId, int & endVsId) {    int teamMemberCapcity = pow(2, winTime);    beginVSId = 1;    endVsId = beginVSId + teamMemberCapcity - 1;    while(1) {        if (curTeamId >= beginVSId && curTeamId <= endVsId) {            break;        } else {            beginVSId = endVsId + 1;            endVsId = beginVSId + teamMemberCapcity - 1;        }    }    int middleId = (beginVSId + endVsId)>>1;    if (curTeamId <= middleId) {        beginVSId = middleId+1;    } else {        endVsId = middleId;    }    // printf("getVSId %d %d %d %d\n", curTeamId, winTime, beginVSId, endVsId);}void beginDP() {    for (int winTime = 1; winTime <= N; winTime++) {        for (int teamId = 1; teamId <= teamNum; teamId++) {            if (winTime == 1) {                if (teamId%2) { // is odd, VS teamId +1                    DP[teamId][1] = P[teamId][teamId+1];                } else { // is even, VS teamId -1                    DP[teamId][1] = P[teamId][teamId-1];                }                // printf("DP %d %d %lf\n", teamId, 1, DP[teamId][1]);            } else {                int beginVSId;                int endVsId;                getVSId(teamId, winTime, beginVSId, endVsId);                double tmp = 0.0;                for (int i = beginVSId; i <= endVsId; i++) {                    tmp += DP[i][winTime-1]*P[teamId][i];                    // // printf("%lf %lf %lf %lf\n",                    //     // DP[i][winTime-1], P[teamId][i],                    //     DP[i][winTime-1]*P[teamId][i], tmp);                }                DP[teamId][winTime] = tmp*DP[teamId][winTime-1];                // printf("DP %d %d %lf %d %d\n", teamId, winTime, DP[teamId][winTime], beginVSId, endVsId);            }        }    }}void getMostPossibleWinner() {    beginDP();    int winnerId = 0;    double maxP = 0.0;    for (int i = 1; i<= teamNum; i++) {        if (maxP < DP[i][N]) {            maxP = DP[i][N];            winnerId = i;        }    }    // printf("%d %lf\n", winnerId, maxP);    printf("%d\n", winnerId);}int main() {    while(scanf("%d", &N) != EOF) {        if (N == -1) {            return 0;        }        teamNum = pow(2, N);        memset(P, 0, sizeof(P));        memset(DP, 0, sizeof(DP));        for (int i = 1; i <= teamNum; ++i) {            for (int j = 1; j <= teamNum; ++j) {                double Pij;                scanf("%lf", &Pij);                P[i][j] = Pij;            }        }        getMostPossibleWinner();    }}

披着概率外衣的DP基础题,在写getVsID时犯了个低级错误.

题目咋一看比较唬人,不过结合着例子分析一下,DP的关系就出来了:

首先,题目指明了,比赛的对手安排:

1对2, 3对4 ...... N-1对N.

那么,对于1来说,赢一场的概率就是 P(1,2)(1击败2的概率)

而对于第二轮比赛,按照题目的规定,

1的选手只能是 3或者4,那么赢得概率就是 P(1,2)( P(1,3)*P(3,4) + P(1,4)*P(4,3) )

就这样递推,会发现,

某个选手i, 赢得N场比赛的概率DP[i][N]可以这样表示:

DP[i][N-1] (DP[A1][N-1]P(i,A1) + DP[A2][N-1]P(i,A3) + ... + DP[AN][N-1]P(i,AN))(A1... AN是 i在第N场比赛中可能遭遇的对手),

那么如何确定A1....AN, 可以观察发现:

对于1来说,第一场比赛的对手只可能是2,

第二场比赛的对手只可能是 3, 4

第三场比赛的对手只可能是 5 6 7 8

.......

可以发现,对于某个选手i来说,在第N轮比赛中,他是从一个规模为2的N-1次方的团体中脱颖而出的,而与之相对的也是一个2的N-1次方大小的团队,

这个团队的所有对手都可能会成为i 在第N轮比赛的对手。(对于该团队某个人 j, 能和i比赛的概率就是在此团队胜出的概率,就是DP[j][N-1])

getVsId就是求出与i在第N轮比赛中可能比赛的对手的序号范围,步骤也很简单,

对于第N论比赛,

i 脱颖而出的团队 和 可能做i对手的团队 会一起组成一个  连续   的 2的N次方大小的团队,

先确定这个团队T的范围,然后确定i是在T的前半部分(那么对手就在后半部分),还是后半部分(那么对手就在前半部分)。

最后利用上面的DP递推方程求出即可.

0 0