【動態規劃】有後效性 DP
阿新 • • 發佈:2021-11-14
\(\text{Description}\)
給定一個 \(n\) 個點 \(m\) 條邊的無向連通圖。從 \(1\) 號節點出發,每一步以相等的概率 隨機 選擇當前節點連出去的某條邊,經過這條邊走到下一個節點,獲得等於這條邊的編號的分數。到達 \(n\) 號頂點時結束。請對這 \(m\) 條邊進行編號,使得獲得的總分的期望值 最小。
\(\text{Solution}\)
考慮 \(\rm dp\),設 \(f_u\) 表示到達節點 \(u\) 的期望次數,\(g_i\) 表示經過邊 \(i\) 的期望次數,\(d_u\) 表示節點 \(u\) 的度數。
對於圖中的一條邊 \((u,v)\)
還要注意:
- 由於到 \(n\) 號點就停了,因此 \(n\) 號點對答案沒有貢獻,因此 \(u,v\) 都不能為 \(n\)。
- 一開始就在一號點,所以 \(f_1\) 初始值為 \(1\)。
所以有
\[f_u= \begin{cases} \sum\limits_{(u,v)\in E,v\ne n}\dfrac{f_v}{d_v}+1&u=1\\ \sum\limits_{(u,v)\in E,v\ne n}\dfrac{f_v}{d_v}&u\ne1,u\ne n \end{cases} \]對於邊 \((u,v)\)
即
\[g_{(u,v)}=\dfrac{f_u}{d_u}+\dfrac{f_v}{d_v}\quad u\ne n,v\ne n \]然後將 \(g\) 進行排序,貪心地選擇即可。
但是我們發現一個問題,\(f_v\) 可以推到 \(f_u\),而更新後的 \(f_u\) 又能推到 \(f_v\),這樣就沒法處理了。我們把這種問題稱為 有後效性 \(\rm dp\)。
怎麼處理呢?
我們舉個例子,假設是這樣一張圖:
除 \(5\) 號點之外:
\[\begin{cases} f_1=\dfrac{f_2}{d_2}+\dfrac{f_3}{d_3}+1\\ f_2=\dfrac{f_1}{d_1}+\dfrac{f_4}{d_4}\\ f_3=\dfrac{f_1}{d_1}\\ f_4=\dfrac{f_2}{d_2} \end{cases} \]我們整理一下這個方程組:
這就是一個 \((n-1)\) 元一次方程組,高斯(-約旦)消元即可。
時間複雜度 \(\mathcal{O}(n^3)\)。
\(\text{Code}\)
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
typedef double db;
using namespace std;
const int MAXN = 505;
const int MAXM = 125005;
int cnt;
int head[MAXN], st[MAXM], ed[MAXM], d[MAXN];
struct edge
{
int to, nxt;
}e[MAXM << 1];
void add(int u, int v)
{
e[++cnt] = edge{v, head[u]};
head[u] = cnt;
d[u]++;
}
int n, m;
db a[MAXN][MAXN];
db f[MAXN], g[MAXM];
void Gauss_Jordan()
{
for (int i = 1; i <= n; i++)
{
int mx = i;
for (int j = i + 1; j <= n; j++)
{
if (fabs(a[j][i]) > fabs(a[mx][i]))
{
mx = j;
}
}
if (mx != i)
{
swap(a[i], a[mx]);
}
for (int j = 1; j <= n; j++)
{
if (j != i)
{
db val = a[j][i] / a[i][i];
for (int k = i + 1; k <= n + 1; k++)
{
a[j][k] -= a[i][k] * val;
}
}
}
}
for (int i = 1; i <= n; i++)
{
f[i] = a[i][n + 1] / a[i][i];
}
}
bool cmp(double x, double y)
{
return x > y;
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i++)
{
scanf("%d%d", st + i, ed + i);
add(st[i], ed[i]);
add(ed[i], st[i]);
}
n--;
for (int u = 1; u <= n; u++)
{
a[u][u] = 1;
for (int i = head[u]; i; i = e[i].nxt)
{
int v = e[i].to;
if (v != n + 1)
{
a[u][v] = -1.0 / d[v];
}
}
if (u == 1)
{
a[u][n + 1] = 1;
}
}
Gauss_Jordan();
for (int i = 1; i <= m; i++)
{
g[i] = f[st[i]] / d[st[i]] + f[ed[i]] / d[ed[i]];
}
sort(g + 1, g + m + 1, cmp);
db ans = 0;
for (int i = 1; i <= m; i++)
{
ans += i * g[i];
}
printf("%.3lf\n", ans);
return 0;
}