1. 程式人生 > 實用技巧 >noip2016換教室

noip2016換教室

題目傳送門

一道困擾了我很久的題,好多次的刷題計劃裡都自動略過這道,一部分原因是因為這題目名字和noip2012借教室實在過於相像,另一部分原因,是這道題看起來實在太複雜了......

noipCSP迫在眉睫,於是我開始做這題,做著做著,感覺海星,有點上頭(x

不說了,來看題吧


1.題目解析

題目大意是,現在有一張圖,牛牛每個時間段會從給定的一個點走到另一個點,每個時間段內他可以選擇申請改去另一個點,但這種申請只能進行至多m次。每次申請有失敗的可能,並且申請失敗依舊會算入總數中。已知每次申請成功的概率,求期望走到的最短路。

首先,我們要解決一個問題,什麼是期望?期望又可以稱為帶權平均數,具體可以檢視百度百科(

這裡)。它和求概率的最大差別就是期望在求值的時候會算上權值。可能有點不清楚,讓我舉個栗子解釋一下。

在這道題中,假設現在的選擇是從i走到j,申請改變到x,那麼這段路的期望值就是a[i][x]*p[i](當前最短路乘以申請成功的概率)+a[i][j]*(1-p[i])(申請不成功的情況),這裡p[i]的求值會在後文裡提到。

明白了嗎,接下來,我們就要開始思考該如何解題了


2.一些處理

我們首先要求出幾個點之間的最短路,由於資料量並不是很大,在所有方法裡隨便選一個就好,我直接打了最簡單的 floyed。因為要多次取用,建議直接使用鄰接矩陣(其實就是懶)。當然如果你想用 Dijkstra 或者別的演算法的話,那也可以,隨便......

這部分直接粘板子上去就可以。

void floyed()
{
    for(int k=1;k<=v;k++)
        for(int i=1;i<=v;i++)
            for(int j=1;j<i;j++)
                if(f[i][k]+f[k][j]<f[i][j])
                {
                      f[i][j]=f[i][k]+f[k][j];
                      f[j][i]=f[i][k]+f[k][j];
                }   
}

記得要先給 f 陣列賦好極大值,否則在後來進行決策的時候會被判斷成可以通過(因為中間是取最小值來著)

然後是 dp 方程的狀態。

首先我們需要用 dp 方程記錄當前時間點和換教室的次數,於是先設定兩維 i 和 j 代表當前這次轉移是 i 時間點時已經申請了 j 次交換。又由於每次進行轉移的時候,都有申請更換和不申請更換兩種選擇,所以咱們再新增一維 k 代表此時是否申請了更換。

所以最後的 dp 陣列就是 dp[i][j][k] 代表 i 時間時已經更換了 j 次教室並且當前的更換狀態是 k(0或1)。

下面讓我們來推導 dp 的轉移方程。


3. dp 方程推導

這道題裡最麻煩的部分就是 dp 方程。各種狀態真的寫瘋我......

來分成幾個部分看一下。

對於每一個點,我們都可以列舉當前換了 j 次教室的情況。所以兩重列舉分別如下:

for(int i=2;i<=n;i++)
    {
        for(int j=0;j<=min(i,m);j++)//當 i 小於 m 的時候,最多更換 i 次,所以可以進行剪枝
        {
                
        }
    }

對於每個dp狀態,都有換和不換兩種情況。

如果不換的話,那麼當前的最短路徑就是上一次更換或不更換的最小值。上一次換,這次便應該是從 d[i-1] 走到 c[i]。上一次不換就會麻煩一點,需要計算申請成功和不成功的期望和,方程用語言描述比較麻煩,直接看具體轉移。

dp[i][j][0]=min(dp[i-1][j][0]+f[c[i-1]][c[i]],dp[i-1][j][1]+f[d[i-1]][c[i]]*k[i-1]+f[c[i-1]][c[i]]*(1-k[i-1]));

如果換,那考慮的就是四種情況。

換成功,上一次換了

換失敗,上一次換了

換成功,上一次沒換

換失敗,上一次沒換

接下來迎來本程式碼最長的一句話

dp[i][j][1]=min(dp[i-1][j-1][0]+f[c[i-1]][d[i]]*k[i]+f[c[i-1]][c[i]]*(1-k[i]),dp[i-1][j-1][1]+f[d[i-1]][d[i]]*k[i]*k[i-1]+f[d[i-1]][c[i]]*(1-k[i])*k[i-1]+f[c[i-1]][d[i]]*(1-k[i-1])*k[i]+f[c[i-1]][c[i]]*(1-k[i-1])*(1-k[i]));    

很 不 友 好

然後因為換教室的次數只要小於給定次數就可以滿足,所以我們給所有可能取一個最小值,即可得出答案。


程式碼

#include<bits/stdc++.h>
using namespace std;
int c[2005],d[2005];
double k[2005],dp[2005][2005][2],f[2005][2005];
int n,m,v,e;
void floyed()
{
    for(int k=1;k<=v;k++)
        for(int i=1;i<=v;i++)
            for(int j=1;j<i;j++)
                if(f[i][k]+f[k][j]<f[i][j])
                {
                      f[i][j]=f[i][k]+f[k][j];
                      f[j][i]=f[i][k]+f[k][j];
                }   
}
int main()
{
//    freopen("P1850_18.in","r",stdin);
//    freopen("P1850.out","w",stdout);
    cin>>n>>m>>v>>e;
    for(int i=1;i<=n;i++) cin>>c[i];
    for(int i=1;i<=n;i++) cin>>d[i];
    for(int i=1;i<=n;i++) cin>>k[i];
    
    for(int i=1;i<=v;i++)
        for(int j=1;j<i;j++)
            f[i][j]=f[j][i]=2147483640;
            
    for(int i=1;i<=e;i++) 
    {
        int x,y;
        double z;
        cin>>x>>y>>z;
        f[x][y]=min(f[x][y],z);
        f[y][x]=min(f[y][x],z);
    }
    
    floyed();
    
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<=m;j++)
        {
            dp[i][j][0]=2147483640;
            dp[i][j][1]=2147483640;
        }
    }
    dp[1][0][0]=0;dp[1][1][1]=0;
    
    for(int i=2;i<=n;i++)
    {
        //double road=f[c[i-1]][c[i]];
        for(int j=0;j<=min(i,m);j++)
        {
            dp[i][j][0]=min(dp[i-1][j][0]+f[c[i-1]][c[i]],dp[i-1][j][1]+f[d[i-1]][c[i]]*k[i-1]+f[c[i-1]][c[i]]*(1-k[i-1]));
            if(j!=0) dp[i][j][1]=min(dp[i-1][j-1][0]+f[c[i-1]][d[i]]*k[i]+f[c[i-1]][c[i]]*(1-k[i]),dp[i-1][j-1][1]+f[d[i-1]][d[i]]*k[i]*k[i-1]+f[d[i-1]][c[i]]*(1-k[i])*k[i-1]+f[c[i-1]][d[i]]*(1-k[i-1])*k[i]+f[c[i-1]][c[i]]*(1-k[i-1])*(1-k[i]));    
        }
    } 
    double minn=2147483640;
    for(int i=0;i<=m;i++)
    {
        minn=min(dp[n][i][0],min(dp[n][i][1],minn));
    }
    printf("%.2f",minn);
    return 0;
}