1. 程式人生 > >[NOIp2016] 換教室

[NOIp2016] 換教室

max http spl pro getchar 分開 play 表示 sin

題目類型:期望\(DP\)

傳送門:>Here<

題意:現有\(N\)個時間段,每個時間段上一節課。如果不申請換教室,那麽時間段\(i\)必須去教室\(c[i]\)上課,如果申請換課成功,那麽就可以去教室\(d[i]\)上課。第\(i\)節課申請換教室成功的概率是\(k[i]\)。每個教室是無向圖的一個節點,從一個教室到另一個教室需要耗費的體力是它們之間的最短路。現在,你最多可以申請換\(M\)節課,問耗費體力值最少的期望

解題思路

題意比較復雜。時間段不如理解為時間點。可以概括為:第\(i\)個時間點要麽在\(c[i]\)要麽在\(d[i]\),並且到\(d[i]\)去的期望是\(k[i]\)

。也就是說申請不成功的概率是\(1-k[i]\)

由於教室最多只有\(300\)間,因此最短路直接用\(Floyd\)處理即可。

然後考慮進行期望\(DP\)。容易想到設\(dp[i][j]\)表示前\(i\)個時間點裏,申請\(j\)次的耗費體力值最少的期望。然而我們發現這樣設非常難轉移,因為我們不知道上一節課有沒有換教室。

因此我們增加一維:\(dp[i][j][k]\)表示前\(i\)個時間點裏,申請\(j\)次,並且\(k=0\)\(i\)個時間點在\(c[i]\)\(k=1\)則在\(d[i]\)。這樣就可以轉移了

由於已經轉化為\(DP\)問題,因此我們只需要考慮狀態。分開考慮:

\[ dp[i][j][0] = \begin{cases} dp[i-1][j][0] + dis[c[i-1]][c[i]]\\ dp[i-1][j][1] + (1-k[i-1])*dis[c[i-1]][c[i]] + k[i-1]*dis[d[i-1]][c[i]] \end{cases}\]

對於\(dp[i][j][0]\)的轉移,我們確定了在第\(i\)個時間點一定在教室\(c[i]\),而起點卻不確定。分開考慮乘上概率即可

\[ dp[i][j][1] = \begin{cases} dp[i-1][j-1][0] + (1-k[i])*dis[c[i-1]][c[i]] + k[i]*dis[c[i-1]][d[i]]\\ dp[i-1][j-1][1] +... \end{cases} \]

第二個方程實在太長了(放不下……),可以見代碼。總體思想還是和前面差不多,不一樣的是\(dp[i][j][1]\)不能代表第\(i\)個時間點在教室\(d[i]\),而是都有可能,因此從\(dp[i-1][j-1][1]\)

轉移過來時要分四類討論

Code

註意\(j=0\)也是要討論的。另外,剛開始\(dp\)數組應該無限大,這樣才能在轉移時自動排除不可能的情況

/*By DennyQi 2018*/
#include <cstdio>
#include <queue>
#include <cstring>
#include <algorithm>
#define  r  read()
using namespace std;
typedef long long ll;
const int MAXN = 2010;
const int MAXM = 20010;
const int INF = 1061109567;
inline int read(){
    int x = 0; int w = 1; register char c = getchar();
    for(; c ^ '-' && (c < '0' || c > '9'); c = getchar());
    if(c == '-') w = -1, c = getchar();
    for(; c >= '0' && c <= '9'; c = getchar()) x = (x<<3) + (x<<1) + c - '0'; return x * w;
}
int N,M,V,E,x,y,z;
int c[MAXN],d[MAXN],dis[305][305];
double k[MAXN],dp[MAXN][MAXN][2],ans;
int main(){
//  freopen(".in","r",stdin);
    memset(dis, 0x3f, sizeof(dis));
    N = r, M = r, V = r, E = r;
    for(int i = 1; i <= V; ++i) dis[i][i] = 0;
    for(int i = 1; i <= N; ++i) c[i] = r;
    for(int i = 1; i <= N; ++i) d[i] = r;
    for(int i = 1; i <= N; ++i) scanf("%lf", k+i);
    for(int i = 1; i <= E; ++i){
        x = r, y = r, z = r;
        dis[x][y] = min(dis[x][y], z);
        dis[y][x] = min(dis[y][x], z);
    }
    for(int K = 1; K <= V; ++K){
        for(int i = 1; i <= V; ++i){
            for(int j = 1; j <= V; ++j){
                dis[i][j] = min(dis[i][j], dis[i][K] + dis[K][j]);
            }
        }
    }
    for(int i = 1; i <= N; ++i){
        for(int j = 0; j <= M; ++j){
            dp[i][j][0] = dp[i][j][1] = 99999999.999;
        }
    }
    dp[1][0][0] = dp[1][1][1] = 0;
    for(int i = 2; i <= N; ++i){
        dp[i][0][0] = dp[i-1][0][0] + dis[c[i-1]][c[i]];
        for(int j = 1; j <= min(i,M); ++j){
            dp[i][j][0] = min(dp[i-1][j][0] + dis[c[i-1]][c[i]], dp[i-1][j][1] + (1-k[i-1])*dis[c[i-1]][c[i]] + k[i-1]*dis[d[i-1]][c[i]]);
            dp[i][j][1] = min(dp[i-1][j-1][0] + (1-k[i])*dis[c[i-1]][c[i]] + k[i]*dis[c[i-1]][d[i]], dp[i-1][j-1][1] + (1-k[i-1])*(1-k[i])*dis[c[i-1]][c[i]] + (1-k[i-1])*k[i]*dis[c[i-1]][d[i]] + k[i-1]*(1-k[i])*dis[d[i-1]][c[i]] + k[i-1]*k[i]*dis[d[i-1]][d[i]]);
        }
    }
    ans = 9999999.999;
    for(int j = 0; j <= M; ++j){
        ans = min(ans, min(dp[N][j][0], dp[N][j][1]));
    }
    printf("%.2f", ans);
    return 0;
}

[NOIp2016] 換教室