1. 程式人生 > >[NOIP2016]換教室(概率期望$DP$)

[NOIP2016]換教室(概率期望$DP$)

span 就是 return print 結果 其中 完成 可能 連通圖

其實吧我老早就把這題切了……因為說實話,這道題確實不難啊……李雲龍:比他娘的狀壓DP簡單多了

今天我翻以前在Luogu上寫的題解時,突然發現放錯代碼了,然後被一堆人\(hack\)……藍瘦啊\(ORZ\)

嗯,還是有些點需要註意以下的!以下是今年4月寫的:


\(\mathcal{\color{red}{Description}}\)

對於剛上大學的牛牛來說,他面臨的第一個問題是如何根據實際情況申請合適的課程。

在可以選擇的課程中,有 \(2n\)節課程安排在 \(n\)個時間段上。在第$ i $(\(1 \leq i \leq n\))個時間段上,兩節內容相同的課程同時在不同的地點進行,其中,牛牛預先被安排在教室\(c_i\)

上課,而另一節課程在教室$ d_i$ 進行。

在不提交任何申請的情況下,學生們需要按時間段的順序依次完成所有的 \(n\) 節安排好的課程。如果學生想更換第\(i\) 節課程的教室,則需要提出申請。若申請通過,學生就可以在第 i 個時間段去教室$ d_i$上課,否則仍然在教室 \(c_i\)上課。

由於更換教室的需求太多,申請不一定能獲得通過。通過計算,牛牛發現申請更換第 \(i\) 節課程的教室時,申請被通過的概率是一個已知的實數 \(k_i\),並且對於不同課程的申請,被通過的概率是互相獨立的。

學校規定,所有的申請只能在學期開始前一次性提交,並且每個人只能選擇至多 \(m\)節課程進行申請。這意味著牛牛必須一次性決定是否申請更換每節課的教室,而不能根據某些課程的申請結果來決定其他課程是否申請;牛牛可以申請自己最希望更換教室的\(m\)

門課程,也可以不用完這 $m $個申請的機會,甚至可以一門課程都不申請。

因為不同的課程可能會被安排在不同的教室進行,所以牛牛需要利用課間時間從一間教室趕到另一間教室。

牛牛所在的大學有\(v\)個教室,有\(e\)條道路。每條道路連接兩間教室,並且是可以雙向通行的。由於道路的長度和擁堵程度不同,通過不同的道路耗費的體力可能會有所不同。 當第 \(i\)\(1 \leq i \leq n-1\))節課結束後,牛牛就會從這節課的教室出發,選擇一條耗費體力最少的路徑前往下一節課的教室。

現在牛牛想知道,申請哪幾門課程可以使他因在教室間移動耗費的體力值的總和的期望值最小,請你幫他求出這個最小值。

\(\mathcal{\color{red}{Solution}}\)

那麽對於這道題而言,先捋清楚題目是求什麽的吧:

對於這個無向連通圖,我們將每走一步定義為一個階段。那麽每一個階段都有兩種可能性:\(p_i\)的概率去\(d_i\),但是在所有的\(d[i]\)\((1<=i<=n)\)至多可以走\(m\)個,\((1-p_i)\)的概率去\(c[i]\)。而我們要求的,就是在這\(n\)個階段結束之後的路程最小期望。

那麽其實狀態之間的轉移,我們不難看出有兩種狀態的轉移:從\(d[i-1]\)或從\(c[i-1]\)轉移過來。而因為實際上對於這個\(DP\)而言,因為數據不大,所以不需要優化什麽的\(qwq\),記錄每種狀態是可行的。

那麽很顯然啊,我們首先要預處理出每兩個點之間的最短路來,方便狀態的轉移。而在這裏,最簡單的就是\(Floyd\)\(qwq\)\((n^3\)顯然可以接受\()\)

    for(qwq int k=1;k<=v;k++)
      for(qwq int i=1;i<=v;i++)
        for(qwq int j=1;j<i;j++)
          if(f[i][k]+f[k][j]<f[i][j])
             f[i][j]=f[j][i]=f[i][k]+f[k][j];

然後就是\(DP\)方程了:

我們定義\(dp[i][j][0/1]\)來表示當前為第\(i\)個階段,連同這一次已經用了\(j\)次換教室的機會,當前這次換\((1)\)不換\((0)\)的最小期望路程總和。

那麽轉移就可以如此轉移:

這次不換:
\(dp[i][j][0]=\) \(min(\)上次不換的\(dp+\)這兩次之間的路程 \(~~\)\(~~\)上次概率換了之後的\(dp+p[i]\times\)上次換了的教室與這次不換的教室之間的距離\(+(1-p[i])\times\)上次不換的教室與這次不換的教室之間的距離\()\)

“誒,為什麽上次概率換了之後(即逗號之後的一大串)要加兩個期望啊?”

這個問題就是\(rqy\)大佬給我解決的,現在我要農夫山泉一把了:因為在上一次換教室時是“概率”交換,所以不一定會換呀。所以要把兩種情況的都加上\(qwq\)

到這兒我們就可以發現,其實換教室比不換教室是要多一重狀態的,因為換教室總要牽扯“概率成功”的問題\(qwq\)

那其實接下來的狀態轉移方程就很簡單了:一次換一次不換,遇到不換就\((1-p[i])\),遇到換就\(p[i]\);兩次都不換就不用枚舉概率。而且由於牽扯到兩次都是概率性事件(比如兩次都換)之類的,這個時候需要的就是乘法原理了。

那麽\(DP\)即如下:

#define qwq register 

    for(qwq int i=1;i<=n;i++)
     for(qwq int j=0;j<=m;j++)
       dp[i][j][0]=dp[i][j][1]=999999999;
     
    dp[1][0][0]=dp[1][1][1]=0;
    for(qwq int i=2;i<=n;i++){
      double dist1=f[c[i-1]][c[i]],dist2=f[d[i-1]][c[i]],dist3=f[c[i-1]][d[i]];
      for(qwq int j=0;j<=min(m,i);j++)
       {                     
          dp[i][j][0]=min(dp[i-1][j][0]+dist1,dp[i-1][j][1]+dist2*p[i-1]+dist1*(1-p[i-1]));
          if(j!=0)
          dp[i][j][1]=min(dp[i-1][j-1][0]+dist3*p[i]+dist1*(1-p[i]),dp[i-1][j-1][1]+dist1*(1-p[i-1])*(1-p[i])+dist3*(1-p[i-1])*p[i]+dist2*(1-p[i])*p[i-1]+dist2*p[i-1]*p[i]);
       }   
    }          

總結:遇到期望的題目時一定要全面考慮啊!我們可以發現這個題的\(dp\)方程其實並不難想。

完結撒花!

//感謝rqy大佬qwqqq 
#include<iostream>
#include<cstdio>
#define qwq register 
using namespace std;
double p[10001],f[2001][2001],dp[2001][2001][2];
int a[2001][2001],c[20001],d[20001];
inline double min(double a,double b){
    return a<b?a:b;
}
inline int  qread(){
    int  k = 0;
    char c;
    c = getchar();
    while(!isdigit(c))c = getchar();
    while(isdigit(c)){
        k = (k<<1)+(k<<3)+c-48;
        c = getchar();
    }
    return k ;
}
inline double qread_double()
{
    double k=0;char c=getchar();
    while(!isdigit(c))c=getchar();
    while(isdigit(c))k=k*10+(c-48),c=getchar();
    if(c==‘.‘)
    {
        double base=0.1;c=getchar();
        while(isdigit(c))k=k+(c-48)*base,base/10,c=getchar();
    }
    return k;
}
int main()
{
    int n,m,v,e,a1,b1,c1;
    cin>>n>>m>>v>>e;
    for(qwq int i=1;i<=n;i++)c[i]=qread();
    for(qwq int i=1;i<=n;i++)d[i]=qread();
    for(qwq int i=1;i<=n;i++)p[i]=qread_double();
    
    for(qwq int i=1;i<=v;i++)
     for(qwq int j=1;j<i;j++)
      f[i][j]=f[j][i]=999999999;
   
    for(qwq int i=1;i<=e;i++){
        a1=qread(),b1=qread(),c1=qread();
        f[a1][b1]=f[b1][a1]=min(f[a1][b1],c1);
    }
    
    for(qwq int k=1;k<=v;k++)
      for(qwq int i=1;i<=v;i++)
        for(qwq int j=1;j<i;j++)
          if(f[i][k]+f[k][j]<f[i][j])
             f[i][j]=f[j][i]=f[i][k]+f[k][j];
             
    for(qwq int i=1;i<=n;i++)
        for(qwq int j=0;j<=m;j++)
            dp[i][j][0]=dp[i][j][1]=999999999;
     
    dp[1][0][0]=dp[1][1][1]=0;
    for(qwq int i=2;i<=n;i++){
     double add1=f[c[i-1]][c[i]];
      for(qwq int j=0;j<=min(m,i);j++)
       {                     
          dp[i][j][0]=min(dp[i-1][j][0]+add1,dp[i-1][j][1]+f[d[i-1]][c[i]]*p[i-1]+f[c[i-1]][c[i]]*(1-p[i-1]));
          if(j!=0)
          dp[i][j][1]=min(dp[i-1][j-1][0]+f[c[i-1]][d[i]]*p[i]+f[c[i-1]][c[i]]*(1-p[i]),dp[i-1][j-1][1]+f[c[i-1]][c[i]]*(1-p[i-1])*(1-p[i])+f[c[i-1]][d[i]]*(1-p[i-1])*p[i]+f[d[i-1]][c[i]]*(1-p[i])*p[i-1]+f[d[i-1]][d[i]]*p[i-1]*p[i]);
       }   
    }          
                               
    double hahaha=9999999999;
    for(int i=0;i<=m;i++){
    hahaha=min(dp[n][i][0],min(dp[n][i][1],hahaha));}
    printf("%.2lf",hahaha);
}

\(By\) \(Flower\) _ \(pks\)

[NOIP2016]換教室(概率期望$DP$)