【DP】【期望】\(P1850\)換教室
題目描述
有 \(2n\) 節課程安排在$ n$ 個時間段上。在第 \(i\)(\(1 \leq i \leq n\))個時間段上,兩節內容相同的課程同時在不同的地點進行,其中,牛牛預先被安排在教室 \(c_i\)上課,而另一節課程在教室 \(d_i\)進行。
在不提交任何申請的情況下,學生們需要按時間段的順序依次完成所有的 \(n\)節安排好的課程。如果學生想更換第 \(i\)節課程的教室,則需要提出申請。若申請通過,學生就可以在第 $i $個時間段去教室 \(d_i\) 上課,否則仍然在教室 \(c_i\)上課。
牛牛發現申請更換第\(i\)節課程的教室時,申請被通過的概率是一個已知的實數 \(k_i\),並且對於不同課程的申請,被通過的概率是互相獨立的。
所有的申請只能一次性提交,並且每個人只能選擇至多 \(m\)節課程進行申請。
牛牛所在的大學有 \(v\)個教室,有 \(e\) 條道路。每條道路連接兩間教室,並且是可以雙向通行的。由於道路的長度和擁堵程度不同,通過不同的道路耗費的體力可能會有所不同。 當第 \(i\)(\(1 \leq i \leq n-1\))節課結束後,牛牛就會從這節課的教室出發,選擇一條耗費體力最少的路徑前往下一節課的教室。
現在牛牛想知道,申請哪幾門課程可以使他因在教室間移動耗費的體力值的總和的期望值最小。
輸入格式
第一行四個整數 \(n,m,v,e\)。
第二行 \(n\) 個正整數,第 \(i\)(\(1 \leq i \leq n\))個正整數表示 c\(c_i\),即第\(i\)個時間段牛牛被安排上課的教室;保證 \(1 \le c_i \le v\)。
第三行 \(n\) 個正整數,第 \(i\)(\(1 \leq i \leq n\))個正整數表示 \(d_i\),即第 $i $個時間段另一間上同樣課程的教室;保證 \(1 \le d_i \le v\)。
第四行 \(n\) 個實數,第 \(i\)(\(1 \leq i \leq n\))個實數表示 \(k_i\),即牛牛申請在第 \(i\)個時間段更換教室獲得通過的概率。保證 \(0 \le k_i \le 1\)。
接下來 \(e\) 行,每行三個正整數 \(a_j, b_j, w_j\),表示有一條雙向道路連接教室 \(a_j, b_j\),通過這條道路需要耗費的體力值是 \(w_j\);保證 \(1 \le a_j, b_j \le v\), \(1 \le w_j \le 100\)。
保證 \(1 \leq n \leq 2000\),\(0 \leq m \leq 2000\),\(1 \leq v \leq 300\),\(0 \leq e \leq 90000\)。
保證通過學校裏的道路,從任何一間教室出發,都能到達其他所有的教室。
保證輸入的實數最多包含 \(3\)位小數。
輸出格式
輸出一行,包含一個實數,四捨五入精確到小數點後恰好\(2\)位,表示答案。你的輸出必須和標準輸出完全一樣纔算正確。
測試數據保證四捨五入後的答案和準確答案的差的絕對值不大於 \(4 \times 10^{-3}\)。 (如果你不知道什麼是浮點誤差,這段話可以理解爲:對於大多數的算法,你可以正常地使用浮點數類型而不用對它進行特殊的處理)
樣例
3 2 3 3
2 1 2
1 2 1
0.8 0.2 0.5
1 2 5
1 3 3
2 3 1
2.80
提示
- 道路中可能會有多條雙向道路連接相同的兩間教室。 也有可能有道路兩端連接的是同一間教室。
- 請注意區分\(n,m,v,e\)的意義, \(n\)不是教室的數量, \(m\)不是道路的數量。
\(Solution\)
考慮DP。狀態怎麼設?顯然第幾節課需要在狀態裏。還要保證最多換\(m\)次,因此次數也要在狀態裏。那麼,怎麼從上一節課轉移呢?顯然如果我們不知道上一節課在哪個教室上的,就無法轉移。因此,還要在加一維,記錄當前是否申請換教室。
因此,狀態就是\(f[i][j][k], 1 \leq i \leq n, 0 \leq j \leq m, 0 \leq k \leq 1\),表示前\(i\)節課,換了\(j\)次教室,第\(i\)次換或不換的期望。
至於轉移則比較簡單了
f[i][j][0] = min(f[i - 1][j][0] + w[c[i]][c[i - 1]],
f[i - 1][j][1] + w[c[i]][d[i - 1]] * k[i - 1]
+ w[c[i]][c[i - 1]] * (1 - k[i - 1]));
f[i][j][1] = min(f[i - 1][j - 1][0] + w[c[i]][c[i - 1]] * (1 - k[i]) + w[d[i]][c[i - 1]] * k[i],
f[i - 1][j - 1][1] + w[d[i]][d[i - 1]] * k[i - 1] * k[i]
+ w[c[i]][c[i - 1]] * (1 - k[i - 1]) * (1 - k[i])
+ w[c[i - 1]][d[i]] * (1 - k[i - 1]) * k[i]
+ w[d[i - 1]][c[i]] * k[i - 1] * (1 - k[i]));
如圖爲當前不選擇換教室
如圖爲當前選擇換教室
這道題還有幾點需要主意的,在預處理部分。具體看代碼
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
long long read(){
long long x = 0; int f = 0; char c = getchar();
while(c < '0' || c > '9') f |= c == '-', c = getchar();
while(c >= '0' && c <= '9') x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
return f? -x:x;
}
int n, m, V, E, c[2002], d[2002], w[305][305];
double f[2002][2002][2], k[2002], ans;
int main(){
memset(w, 63, sizeof w);//距離初始化
n = read(); m = read(); V = read(); E = read();
for(int i = 1; i <= n; ++i) c[i] = read();
for(int i = 1; i <= n; ++i) d[i] = read();
for(int i = 1; i <= n; ++i) scanf("%lf", &k[i]);
for(int i = 1; i <= E; ++i){
int x = read(), y = read(), z = read();
w[x][y] = w[y][x] = min(w[x][y], z);//有可能重邊
}
for(int l = 1; l <= V; ++l)
for(int i = 1; i <= V; ++i)
for(int j = 1; j <= V; ++j)
w[i][j] = min(w[i][j], w[i][l] + w[l][j]);//floyed最短路
for(int i = 1; i <= V; ++i) w[i][i] = w[i][0] = w[0][i] = 0;//初始化
for(int i = 1; i <= n; ++i) f[i][0][0] = f[i - 1][0][0] + w[c[i]][c[i - 1]], f[i][0][1] = 1e9;
//初始化,處理一個也不申請的情況
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= m; ++j){
f[i][j][0] = min(f[i - 1][j][0] + w[c[i]][c[i - 1]],
f[i - 1][j][1] + w[c[i]][d[i - 1]] * k[i - 1]
+ w[c[i]][c[i - 1]] * (1 - k[i - 1]));
f[i][j][1] = min(f[i - 1][j - 1][0] + w[c[i]][c[i - 1]] * (1 - k[i])
+ w[d[i]][c[i - 1]] * k[i],
f[i - 1][j - 1][1] + w[d[i]][d[i - 1]] * k[i - 1] * k[i]
+ w[c[i]][c[i - 1]] * (1 - k[i - 1]) * (1 - k[i])
+ w[c[i - 1]][d[i]] * (1 - k[i - 1]) * k[i]
+ w[d[i - 1]][c[i]] * k[i - 1] * (1 - k[i]));
}
double ans = f[n][0][0];
for(int i = 1; i <= m; ++i) ans = min(ans, min(f[n][i][1], f[n][i][0]));//更新答案
printf("%.2lf", ans);
return 0;
}