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; }