通往賢者之塔的路上,有許多的危機。c++
咱們能夠把這個地形看作是一顆樹,根節點編號爲1,目標節點編號爲n,其中1-n的簡單路徑上,編號依次遞增,優化
在[1,n]中,一共有n個節點。咱們把編號在[1,n]的叫作正確節點,[n+1,m]的叫作錯誤節點。一個葉子,若是是正spa
確節點則爲正確葉子,不然稱爲錯誤葉子。莎緹拉要幫助昴到達賢者之塔,所以如今面臨着存檔位置設定的問題。code
爲了讓昴成長爲英雄,所以一共只有p次存檔的機會,其中1和n必須存檔。被莎緹拉設置爲要存檔的節點稱爲存檔ip
位置。固然不能讓昴陷入死循環,因此存檔只能在正確節點上進行,並且同一個節點不能存屢次檔。由於通往賢者input
之塔的路上有影響的瘴氣,所以莎緹拉假設昴每次位於樹上一個節點時,都會等機率選擇一個兒子走下去。每當走it
到一個錯誤葉子時,再走一步就會讀檔。具體的,每次昴到達一個新的存檔位置,存檔點便會更新爲這個位置(假io
如如今的存檔點是i,如今走到了一個存檔位置j>i,那麼存檔點便會更新爲j)。讀檔的意思就是回到當前存檔點class
。初始昴位於1,當昴走到正確節點n時,便結束了路程。莎緹拉想知道,最優狀況下,昴結束路程的指望步數是多循環
少?
第一行一個正整數T表示數據組數。
接下來每組數據,首先讀入三個正整數n,m,p。
接下來m-n行,描述樹上全部的非正確邊(正確邊即鏈接兩個正確節點的邊)
用兩個正整數j,k表示j與k之間有一條連邊,j和k能夠均爲錯誤節點,也能夠一個爲正確節點另外一個爲錯誤節點。
數據保證j是k的父親。
50<=p<=n<=700,m<=1500,T<=5。
數據保證每一個正確節點均有至少2個兒子,至多3個兒子。
T行每行一個實數表示每組數據的答案。請保留四位小數。
1
3 7 2
1 4
2 5
3 6
3 7
9.0000
發現其實仍是在一條鏈上進行dp,剩下的分支均可以直接與處理作掉
首先求出從每一個點進入壞點返回存檔點的指望步數\(f_{i}\)
而後能夠算一下以每個點i爲最新存檔點到點j的指望步數(途中沒有存檔點)\(w_{i,j}\)
首先設\(cur\)爲從\(j-1\)到\(j\)的指望步數
能夠獲得\(cur=\frac{1}{d_{j-1}}+\frac{d_{j-1}-1}{d_{j-1}}*(f_{j-1}+w_{i,j-1}+cur)\)
又由於\(w_{i,j}=w_{i,j-1}+cur\)
因此\(w_{i,j}=w_{i,j-1} * d_{j-1} + f_{j-1} * (d_{j-1} - 1) + 1\)
而後就能夠dp了
記錄\(dp_{i,j}\)表示前i個點選了j個存檔位置,最新的存檔位置是i的最小指望步數
咱們想用決策單調性優化,因此就要從
\(dp_{j,x}+w_{j,i}\le dp_{k,x}+w_{k,i}(k<j)\)
推到
\(dp_{j,x}+w_{j,i+1}\le dp_{k,x}+w_{k,i+1}(k<j)\)
也就是說要證實\(w_{k,i}-w_{j,i}\le w_{k,i+1}-w_{j,i+1}\)
轉化成\(w_{k,i}+w_{j,i+1}\le w_{k,i+1}+w_{j,i}\)
其中\(k<j\le i<i+1\)
而後對於w
咱們來推一下式子看看是否是
\(w_{i,j}+w_{i+1,j+1}\le w_{i+1,j}+w_{i,j+1}\)
左邊\(w_{i,j}+w_{i+1,j+1}=w_{i,j}+w_{i+1,j}*d_j+f_j*(d_j-1)+1\)
右邊\(w_{i+1,j}+w_{i,j+1}=w_{i+1,j}+w_{i,j}*d_j+f_j*(d_j-1)+1\)
發現是知足的,而後就直接按照套路維護就能夠了
#include <bits/stdc++.h> using namespace std; typedef long double ld; const int N = 2010; int n, m, p, d[N]; vector<int> g[N]; ld f[N], w[N][N], dp[N][N]; struct Node { int pos, l, r; Node() {} Node(int pos, int l, int r): pos(pos), l(l), r(r) {} } que[N]; void init() { for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { w[i][j] = 0; dp[i][j] = 1e9; } } for (int i = 1; i <= m; i++) { f[i] = 0; d[i] = 0; g[i].clear(); } } void dfs(int u) { if (!(signed) g[u].size()) { f[u] = 1; return; } for (int i = 0; i < (signed) g[u].size(); i++) { int v = g[u][i]; dfs(v); f[u] += (f[v] + 1.0) / (1.0 * d[u]); } } ld calc(int i, int j, int k) { return dp[j][k - 1] + w[j][i]; } int find(Node a, int b, int k) { int l = a.l, r = n, res = n; while (l <= r) { int mid = (l + r) >> 1; if (calc(mid, a.pos, k) >= calc(mid, b, k)) res = mid, r = mid - 1; else l = mid + 1; } return res; } void solve() { scanf("%d %d %d", &n, &m, &p); p--; init(); for (int i = 1; i <= m - n; i++) { int u, v; scanf("%d %d", &u, &v); g[u].push_back(v); ++d[u]; } for (int i = n; i >= 1; i--) dfs(i); for (int i = 1; i <= n - 1; i++) ++d[i]; for (int i = n; i >= 1; i--) { for (int j = i + 1; j <= n; j++) { w[i][j] = w[i][j - 1] * d[j - 1] + f[j - 1] * (d[j - 1] - 1) + 1; } } dp[1][1] = 0; for (int i = 2; i <= p; i++) { int ql = 1, qr = 1; que[ql] = Node(i - 1, i - 1, n); for (int j = i; j < n; j++) { if (ql <= qr && ++que[ql].l > que[ql].r) ql++; dp[j][i] = calc(j, que[ql].pos, i); if (ql <= qr && calc(n, que[qr].pos, i) <= calc(n, j, i)) continue; while (ql <= qr && calc(que[qr].l, que[qr].pos, i) >= calc(que[qr].l, j, i)) qr--; if (ql > qr) { que[++qr] = Node(j, j, n); } else { int cur = find(que[qr], j, i); que[qr].r = cur - 1; que[++qr] = Node(j, cur, n); } } } ld ans = 1e9; for (int i = 1; i < n; i++) { ans = min(ans, dp[i][p] + w[i][n]); } printf("%.4Lf\n", ans); } int main() { #ifdef dream_maker freopen("input.txt", "r", stdin); #endif int T; scanf("%d", &T); while (T--) solve(); return 0; }