1. 程式人生 > 實用技巧 >「NOIP 2016」換教室

「NOIP 2016」換教室

Description

\(n\) 節課,第 \(i\) 節課可以選擇在教室 \(C_i\) 上課,也可以選擇申請換教室,到教室 \(D_i\) 上課。若選擇申請換課,則有 \(K_i\) 的概率申請通過,另外 \(1-K_i\) 的概率依然留在 \(C_i\) 教室。

共有 \(v\) 個教室,\(e\) 條路徑雙向連通教室 \(X_i\)\(Y_i\),邊權為 \(W_i\)。在每兩節課之間,你要從上一個教室走到下一個教室。

最多提交 \(m\) 個申請,並且得在第一節課上課之前一次性提交所有申請(也就是,不能根據某一次申請的通過情況來選擇下一次申請),要使自己走的路的期望最小。求最優策略下的總距離的期望。

\(1\leq n\leq 2000,0\leq m \leq 2000,1\leq v\leq 300,0\leq e\leq 9\times 10^4,1\leq W_i \leq 1000,0\leq K_i\leq 1\)

Solution

考慮 DP,用 \(dp[i][j][0/1]\) 表示前 \(i\) 節課申請了 \(j\) 次,第 \(i\) 次是否申請的期望最小值。

先用 Floyd 預處理出兩個教室之間的最短路長度 \(dis(i,j)\)

\(dp[i][j][0]\) 所記錄的狀態一定處在教室 \(c[i]\)\(dp[i][j][1]\) 所記錄的狀態有 \(k[i]\) 的概率處在 \(d[i]\)

,另 \(1-k[i]\) 的概率處在 \(c[i]\)

討論第 \(i\) 次是否申請。\(P_1,P_2\) 是臨時變數,分別存第 \(i-1\) 節課“申請/不申請”兩種方案的期望總距離。

若不申請:

\(P_1=dp[i-1][j][0]+dis(c[i-1],c[i])\)

\(P_2=dp[i-1][j][1]+k[i-1]\times dis(d[i-1],c[i])+(1-k[i-1])\times dis(c[i-1],c[i])\)

\(dp[i][j][0]=min(P_1,P_2)\)

即在兩種策略中選取期望值較小的。

若申請:

\(P_1=dp[i-1][j-1][0]+k[i]\times dis(c[i-1],d[i])+(1-k[i])\times dis(c[i-1],c[i])\)

\(P_2=dp[i-1][j-1][1]+(1-k[i-1])\times k[i]\times dis(c[i-1],d[i])\\+(1-k[i-1])\times (1-k[i])\times dis(c[i-1],c[i])\\+k[i-1]\times k[i]\times dis(d[i-1],d[i])\\+k[i-1]\times (1-k[i])\times dis(d[i-1],c[i])\)

\(dp[i][j][1]=min(P_1,P_2)\)

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2010,M=310,inf=1e18;
int n,m,v,e,c[N],d[N],x,y,z,f[M][M];
double k[N],dp[N][N][2],p1,p2,ans=inf; 
signed main(){
    scanf("%lld%lld%lld%lld",&n,&m,&v,&e);
    for(int i=1;i<=n;i++)
        scanf("%lld",&c[i]);
    for(int i=1;i<=n;i++)
        scanf("%lld",&d[i]);
    for(int i=1;i<=n;i++)
        scanf("%lf",&k[i]);
    //初始化兩點之間的最短路 
    for(int i=1;i<=v;i++)
        for(int j=1;j<=v;j++)
            f[i][j]=f[j][i]=inf;
    for(int i=1;i<=e;i++){
        scanf("%lld%lld%lld",&x,&y,&z);
        f[x][y]=f[y][x]=min(f[x][y],z); 
    }
    for(int i=1;i<=v;i++)
        for(int j=1;j<=v;j++) f[i][i]=0;
    //floyd 求任意兩點之間的最短路 
    for(int k=1;k<=v;k++)
        for(int i=1;i<=v;i++)
            for(int j=1;j<=v;j++)
                f[i][j]=min(f[i][j],f[i][k]+f[k][j]);
    //初始化期望 
    for(int i=0;i<=n;i++)
        for(int j=0;j<=m;j++)
            dp[i][j][0]=dp[i][j][1]=inf;    //DP 之前的初值概率均為無窮大便於比較
    //DP 計算期望 
    dp[1][0][0]=dp[1][1][1]=0;    //DP 初值 
    for(int i=2;i<=n;i++)
        for(int j=0;j<=min(i,m);j++){
            p1=dp[i-1][j][0]+f[c[i-1]][c[i]],p2=dp[i-1][j][1]+k[i-1]*f[d[i-1]][c[i]]+(1-k[i-1])*f[c[i-1]][c[i]];
            dp[i][j][0]=min(dp[i][j][0],min(p1,p2));
            if(j){
                p1=dp[i-1][j-1][0]+k[i]*f[c[i-1]][d[i]]+(1-k[i])*f[c[i-1]][c[i]];
                p2=dp[i-1][j-1][1]+(1-k[i-1])*k[i]*f[c[i-1]][d[i]]+(1-k[i-1])*(1-k[i])*f[c[i-1]][c[i]]+k[i-1]*k[i]*f[d[i-1]][d[i]]+k[i-1]*(1-k[i])*f[d[i-1]][c[i]];
                dp[i][j][1]=min(dp[i][j][1],min(p1,p2));
            } 
        }
    //計算答案 
    for(int i=0;i<=m;i++)
        ans=min(ans,min(dp[n][i][0],dp[n][i][1]));
    printf("%.2lf\n",ans);
    return 0;
}