【期望DP】


【總覽】

【期望dp】

  求解達到某一目標的期望花費:因為最終的花費無從知曉(不可能從$\infty$推起),所以期望dp需要倒序求解。

  設$f[i][j]$表示在$(i, j)$這個狀態實現目標的期望值(相當於是差距是多少)。

首先$f[n][m] = 0$,在目標狀態期望值為0。然后$f = (\sum f' × p) + w $,$f'$為上一狀態(距離目標更近的那個,倒序),$p$為從$f$轉移到$f'$的概率(則從$f'$轉移回$f$的概率也為$p$),w為轉移的花費。

最后輸出初始位置的$f$即可。

 

特別的,當轉移關系不成環時,期望dp可以線性遞推。

但當轉移關系成環時,期望dp的最終狀態相當於一個已知量,而轉移關系相當於一個個方程,可以使用【高斯消元】解決。

高斯消元期望dp的例題

【概率dp】

  概率dp通常已知初始的狀態, 然后求解最終達到目標的概率,所以概率dp需要順序求解。

  概率dp相對簡單,當前狀態只需加上所有上一狀態乘上轉移概率即可:$f = \sum f'_{i} × p_{i}$

【例題】

【hdu3853】Loops

  簡單的期望dp題,設$f[i][j]$表示當前位置到達終點的期望體力,則$f[r][c] = 0$。

  已知每個位置不動、向下、向右的概率。設p0為當前狀態下停留的概率,p1為向下的概率,p2為向右的概率,那么就從終點開始逆推:

$$f[i][j] = p0 × f[i][j] + p1 × f[i + 1][j] + p2 × f[i][j +1] + 2$$

  dp強調根據已知推未知,發現等號右邊$f[i][j]$正是我們要求的,呢么這就可以構成一個方程了。不過沒有那么復雜,因為轉移關系不是一個環,只要我們將右邊的$f[i][j]$移到左邊,再將系數除過去,等號右邊就都是已知的了。

【CODE】

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<vector>
#include<algorithm>
#include<cmath>
using namespace std;

const int R = 1005, C = 1005;
const double eps = 1e-5;
int r, c;
double p[R][C][3];
double f[R][C];

int main(){
    while(scanf("%d%d", &r, &c) != EOF){
        memset(p, 0, sizeof p);
        memset(f, 0, sizeof f);
        for(int i = 1; i <= r; i++)
            for(int j = 1; j <= c; j++)
                scanf("%lf%lf%lf", &p[i][j][0], &p[i][j][1], &p[i][j][2]);
        f[r][c] = 0;
        for(int i = r; i >= 1; i--)
            for(int j = c; j >= 1; j--){
                if(i == r && j == c) continue;
                if(fabs(1.0 - p[i][j][0]) < eps) continue;
                f[i][j] = (p[i][j][1] * f[i][j + 1] + p[i][j][2] * f[i + 1][j] + 2.0) / (1.0 - p[i][j][0]);
            }
        printf("%.3f\n", f[1][1]);
    }
    return 0;
}
View Code

【hdu4405】AeroplaneChess

  又是一道期望dp。讀題可知終點落在$n$~ $n + 5$,將它們的f全部置為$0$。

  因為有直接跳轉,所以如果當前點有可以直接跳轉到的點,那么這次是不用擲骰子的,因為當前期望等於目標點的期望。

  然后考慮擲色子,搖到$1, 2, 3, 4, , 6$的概率都為$\frac{1}{6}$,所以$f[i] = \sum_{x = 1}^{6} f[i + x] × \frac{1}{6} + 1$

  這樣倒序dp便可以得到期望值。

【CODE】

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<vector>
#include<algorithm>
#include<cmath>
using namespace std;

const int N = 100050;
int go[N];
int n, m;
double f[N];

int main(){
    while(~scanf("%d%d", &n, &m), n + m){
        memset(go, -1, sizeof go);
        for(int i = 1; i <= m; i++){
            int x, y; scanf("%d%d", &x, &y);
            go[x] = y;
        }
        memset(f, 0, sizeof f);
        for(int i = n - 1; i >= 0; i--){
            if(go[i] != -1){
                f[i] = f[go[i]];
                continue;
            }
            f[i] = (f[i + 1] + f[i + 2] + f[i + 3] + f[i + 4] + f[i + 5] + f[i + 6]) / 6 + 1;
        }
        printf("%.4f\n", f[0]);
    }
    return 0;
}
View Code

 【poj2096】收集錯誤

  這道題很有意思。設$f[i][j]$為收集到$i$種bug,屬於$j$個子系統的期望天數,同樣$f[n][s] = 0$

  考慮當前bug:

  •  屬於已經收集到的$i$種,也屬於已經收集到的$j$個系統,概率為$\frac{i × j}{n × s}$
  •     屬於已經收集到的$i$種,屬於新的一套系統, 概率為$\frac{i × (s - j)}{n × s}$
  •    屬於新的一種,屬於已經收集到的$j$個系統,概率為$\frac{(n - i) × j}{n × s}$
  •     屬於新的一種,屬於新的系統,概率為$\frac{(n - i) × (s - j)}{n × s}$

上面順推求出的概率,應該是等於逆推的概率的。

其余的就很基礎了。

【CODE】

#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<vector>
using namespace std;

const int N = 1005, S = 1005;
double f[N][S];
int n, s;

int main(){
    scanf("%d%d", &n, &s);
    f[n][s] = 0.0;
    for(int i = n; i >= 0; i--){
        for(int j = s; j >= 0; j--){
            if(n * s - i * j == 0) continue;
            double c1 = (double)i * ((double)s - (double)j), c2 = ((double)n - (double)i) * (double)j, 
                   c3 = ((double)n - (double)i) * ((double)s - (double)j), c4 = (double)n * (double)s, c5 = (double)n * (double)s - (double)i * (double)j;
            f[i][j] = ((c1 * f[i][j + 1] + c2 * f[i + 1][j] + c3 * f[i + 1][j + 1] + c4) / c5);
        }
    }
    printf("%.4f\n", f[0][0]);
    return 0;
}
View Code

 【poj3071】FootBall

  終於到概率dp了。設$f[i][j]$表示當前第$i$輪比賽,$j$隊獲勝的概率,那么他如果想獲勝:

  • 首先上一輪比賽他必須獲勝。
  • 然后他的對手上一輪必須獲勝。
  • 他的對手只能是相鄰的。

  判斷相鄰十分巧妙的使用了二進制:如果把所有隊伍的編號都$-1$:

  從$0$開始的自然數(二進制):$0, 1, 10, 11, 100, 101, ......$

  可以發現相鄰的數它們的最后一位一定相反。

  進行第一輪比賽后,相當於將相鄰倆個節點替換成他們的父節點$(k >> 1)即將最后一位去掉$,此時相鄰的點仍然符合規律。

  所以我們判斷兩隊是否能比賽的標准就是:$(j >> (i - 1)) $ ^ $1 == k >> (i - 1)$

【CODE】

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#include<vector>
using namespace std;

const int N = 8;
int n;
double f[N][300], p[300][300];

int main(){
    freopen("h.in", "r", stdin);
    while(scanf("%d", &n), n != -1){
        
        memset(p, 0, sizeof p);
        memset(f, 0, sizeof f);
        for(int i = 1; i <= (1 << n); i++){
            f[0][i] = 1;
            for(int j = 1; j <= (1 << n) ; j++)
                scanf("%lf", &p[i][j]);
        }
        for(int i = 1; i <= n; i++)
            for(int j = 1; j <= (1 << n); j++)
                for(int k = 1; k <= (1 << n); k++)
                    if((((j - 1) >> (i - 1)) ^ 1) == ((k - 1) >> (i - 1)))
                        f[i][j] += f[i - 1][k] * f[i - 1][j] * p[j][k];
        double ans = -1;
        int ret = 0;
        for(int i = 1; i <= (1 << n); i++)
            if(ans < f[n][i]) ans = max(ans, f[n][i]), ret = i;
        printf("%d\n", ret);
    }
}
View Code


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM