題目傳送門
前言
終于自己想出概率期望 d p dp dp 的狀態了,但是依舊沒能相對轉移方程。(招笑)
暴力
這題部分分和特殊情況分給的挺多的,所以先拿部分分。
一、思路
- 先跑一邊 F l o y d Floyd Floyd 最短路求出兩點間最短距離 d i s i , j dis_{i, j} disi,j?。
- 對于 m = 0 m = 0 m=0,答案就是 ∑ i = 1 n ? 1 d i s c i , c i + 1 \sum_{i = 1}^{n - 1} dis_{c_{i}, c_{i + 1}} ∑i=1n?1?disci?,ci+1??;
對于所有 n ≤ 20 n \leq 20 n≤20,直接用狀態壓縮,二進制枚舉所有 【申請情況】 與 【同意情況】(注意:這兩者不一樣,因為申請了不一定同意,所以枚舉申請情況之下還要枚舉同意情況),直接計算。
二、復雜度
- 空間: O ( v 2 + n ) O(v^2 + n) O(v2+n)。
- 時間:對于 m = 0 m = 0 m=0, O ( v 3 + n ) O(v^3 + n) O(v3+n);對于 n ≤ 20 n \leq 20 n≤20, O ( v 3 + n × 2 n × 2 m ) O(v^3 + n \times 2^n \times 2^m) O(v3+n×2n×2m)。
(當然后者的時間復雜度遠遠跑不滿,因為對于多數數據 m m m 很小,會限制枚舉的狀態數量)
三、代碼
#include <bits/stdc++.h>using namespace std;const int maxn = 2e3 + 7;
const int maxv = 3e2 + 7;
const int inf = 0x3f3f3f3f;int n, m, v, e;
int c[maxn], d[maxn];
double p[maxn];
int dis[maxv][maxv];
void Floyd() {for (int k = 1; k <= v; ++k)for (int i = 1; i <= v; ++i)for (int j = 1; j <= v; ++j)dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);
}
double ans;
int main() {scanf("%d%d%d%d", &n, &m, &v, &e);for (int i = 1; i <= n; ++i) scanf("%d", c + i);for (int i = 1; i <= n; ++i) scanf("%d", d + i);for (int i = 1; i <= n; ++i) scanf("%lf", p + i);memset(dis, inf, sizeof(dis));for (int i = 1; i <= v; ++i) dis[i][i] = dis[0][i] = dis[i][0] = 0;for (int i = 1, a, b, w; i <= e; ++i) {scanf("%d%d%d", &a, &b, &w);dis[a][b] = dis[b][a] = min(w, dis[a][b]);}Floyd();if (m == 0) {for (int i = 1; i < n; ++i)ans += dis[c[i]][c[i + 1]];printf("%.2lf\n", ans);return 0;}ans = 2e9;for (int s = 0; s < (1 << n); ++s) { // 枚舉【申請情況】if (__builtin_popcount(s) > m) continue; // 用來計算 s 二進制中有多少個 1 的函數double sum = 0; // 此【申請情況】下的期望for (int ss = s; 1; ss = s & (ss - 1)) { // 枚舉【同意情況】double tmp = 0; // 此【同意情況】對【申請情況】的貢獻for (int i = 2; i <= n; ++i) {int lst = ((ss & (1 << (i - 1 - 1))) ? d[i - 1] : c[i - 1]);int now = ((ss & (1 << (i - 1))) ? d[i] : c[i]);tmp += dis[lst][now];}for (int i = 1; i <= n; ++i)if (ss & (1 << (i - 1))) tmp *= p[i];else if (s & (1 << (i - 1))) tmp *= 1 - p[i];sum += tmp;if (ss == 0) break;}ans = min(ans, sum);}printf("%.2lf\n", ans);return 0;
}
期望得分 68 p t s 68pts 68pts,實際得分 68 p t s 68pts 68pts。
(洛谷測評機跑出來挺迷的,應該是數據水的問題,前面暴力的數據點一部分沒過,后面正解的數據點過了一部分)
正解
概率期望的題一般就是拿 d p dp dp 做。
一、思路
狀態設計
- 很明顯要有兩維的狀態 i , j i, j i,j,表示前 i i i 個中選了 j j j 個。
但是由于花費還與上一狀態有關(上一個課程是在 c i c_i ci? 還是 d i d_i di?),所以還要一維表示這個課是否被申請了。 - 設 d p i , j , 0 / 1 dp_{i, j, 0/1} dpi,j,0/1? 表示在前 i i i 個課中 申請了(注意不是同意!!) j j j 個,且第 i i i 個課【沒被申請 / 被申請了】時的最小期望。
狀態轉移
- 若當前課程 i i i 不申請,且上一個課程也沒有申請,那么轉移方程為: d p i , j , 0 = m i n ( d p i , j , d p i ? 1 , j , 0 + d i s c i ? 1 , c i ) dp_{i, j, 0} = min(dp_{i, j}, dp_{i - 1, j, 0} + dis_{c_{i - 1}, c_i}) dpi,j,0?=min(dpi,j?,dpi?1,j,0?+disci?1?,ci??)
- 若當前課程 i i i 不申請,但上一個課程申請了,那么轉移方程為(轉移條件為 j > 0 j > 0 j>0): d p i , j , 0 = m i n ( d p i , j , 0 , d p i ? 1 , j , 1 + d i s c i ? 1 , c i × ( 1 ? k i ) + d i s d i ? 1 , c i × k i ) dp_{i, j, 0} = min( dp_{i, j, 0}, dp_{i - 1, j, 1} + dis_{c_{i - 1}, c_{i}} \times (1 - k_i) + dis_{d_{i - 1}, c_i} \times k_{i}) dpi,j,0?=min(dpi,j,0?,dpi?1,j,1?+disci?1?,ci??×(1?ki?)+disdi?1?,ci??×ki?)后面的分別對應【上一次申請沒通過】和【上一次申請通過了】;
- 若當前課程 i i i 申請,那么上一次可以申請,也可以不申請,總共有四種情況,在此就不列舉出來了(條件當然也是 j > 0 j > 0 j>0)。
邊界條件
- 只有一個課程時,申請或不申請期望花費都為 0 0 0,即: d p 1 , 0 , 0 = d p 1 , 1 , 1 = 0 dp_{1,0,0} = dp_{1,1,1} = 0 dp1,0,0?=dp1,1,1?=0。
- 而對于 d p 1 , 0 , 1 , d p 1 , 1 , 0 dp_{1,0,1},dp_{1,1,0} dp1,0,1?,dp1,1,0? 這種不合法狀態,我們可以先把他們設為正無窮,這樣就不會從它門轉移了。
答案
- 因為題目說可以不用玩 m m m 次申請,所以答案就是 m i n i = 0 m { d p n , i , 0 , d p n , i , 1 } min_{i = 0}^{m} \left\{dp_{n, i,0}, dp_{n, i, 1} \right\} mini=0m?{dpn,i,0?,dpn,i,1?}。
復雜度
- 空間: O ( v 2 + n × m ) O(v^2 + n \times m) O(v2+n×m);
- 時間: O ( v 3 + n × m ) O(v^3 + n \times m) O(v3+n×m)。
二、代碼
#include <bits/stdc++.h>using namespace std;const int maxn = 2e3 + 7;
const int maxv = 3e2 + 7;
const int inf = 0x3f3f3f3f;int n, m, v, e;
int c[maxn], d[maxn];
double p[maxn];
int dis[maxv][maxv];
void Floyd() {for (int k = 1; k <= v; ++k)for (int i = 1; i <= v; ++i)for (int j = 1; j <= v; ++j)dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);
}double dp[maxn][maxn][2];
double ans;
int main() {scanf("%d%d%d%d", &n, &m, &v, &e);for (int i = 1; i <= n; ++i) scanf("%d", c + i);for (int i = 1; i <= n; ++i) scanf("%d", d + i);for (int i = 1; i <= n; ++i) scanf("%lf", p + i);memset(dis, 63, sizeof(dis));for (int i = 1; i <= v; ++i) dis[i][i] = dis[i][0] = dis[0][i] = 0;for (int i = 1, a, b, w; i <= e; ++i) {scanf("%d%d%d", &a, &b, &w);dis[a][b] = dis[b][a] = min(w, dis[a][b]);}Floyd();for (int i = 0; i <= n; ++i)for (int j = 0; j <= m; ++j)dp[i][j][0] = dp[i][j][1] = 2e9;dp[1][0][0] = dp[1][1][1] = 0;for (int i = 2; i <= n; ++i) {dp[i][0][0] = dp[i - 1][0][0] + dis[c[i - 1]][c[i]];for (int j = 1; j <= min(i, m); ++j) {// 巨丑馬蜂dp[i][j][0] = min(dp[i - 1][j][0] + dis[c[i - 1]][c[i]],dp[i - 1][j][1] + dis[c[i - 1]][c[i]] * (1 - p[i - 1]) + dis[d[i - 1]][c[i]] * p[i - 1]);dp[i][j][1] = min(dp[i][j][1], dp[i - 1][j - 1][0] + dis[c[i - 1]][c[i]] * (1 - p[i]) + dis[c[i - 1]][d[i]] * p[i]);dp[i][j][1] = min(dp[i][j][1], dp[i - 1][j - 1][1] + dis[c[i - 1]][c[i]] * (1 - p[i - 1]) * (1 - p[i]) + dis[c[i - 1]][d[i]] * (1 - p[i - 1]) * p[i] + dis[d[i - 1]][c[i]] * p[i - 1] * (1 - p[i]) + dis[d[i - 1]][d[i]] * p[i - 1] * p[i]);}}ans = 2e9;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;
}
期望的分 100 p t s 100pts 100pts,實際得分 100 p t s 100pts 100pts。