1. 程式人生 > >BZOJ4899: 記憶的輪廓【概率期望DP】【決策單調性優化DP】

BZOJ4899: 記憶的輪廓【概率期望DP】【決策單調性優化DP】

Description

通往賢者之塔的路上,有許多的危機。

我們可以把這個地形看做是一顆樹,根節點編號為1,目標節點編號為n,其中1-n的簡單路徑上,編號依次遞增,

在[1,n]中,一共有n個節點。我們把編號在[1,n]的叫做正確節點,[n+1,m]的叫做錯誤節點。一個葉子,如果是正

確節點則為正確葉子,否則稱為錯誤葉子。莎緹拉要幫助昴到達賢者之塔,因此現在面臨著存檔位置設定的問題。

為了讓昴成長為英雄,因此一共只有p次存檔的機會,其中1和n必須存檔。被莎緹拉設定為要存檔的節點稱為存檔

位置。當然不能讓昴陷入死迴圈,所以存檔只能在正確節點上進行,而且同一個節點不能存多次檔。因為通往賢者

之塔的路上有影響的瘴氣,因此莎緹拉假設昴每次位於樹上一個節點時,都會等概率選擇一個兒子走下去。每當走

到一個錯誤葉子時,再走一步就會讀檔。具體的,每次昴到達一個新的存檔位置,存檔點便會更新為這個位置(假

如現在的存檔點是i,現在走到了一個存檔位置j>i,那麼存檔點便會更新為j)。讀檔的意思就是回到當前存檔點

。初始昴位於1,當昴走到正確節點n時,便結束了路程。莎緹拉想知道,最優情況下,昴結束路程的期望步數是多

少?

Input

第一行一個正整數T表示資料組數。

接下來每組資料,首先讀入三個正整數n,m,p。

接下來m-n行,描述樹上所有的非正確邊(正確邊即連線兩個正確節點的邊)

用兩個正整數j,k表示j與k之間有一條連邊,j和k可以均為錯誤節點,也可以一個為正確節點另一個為錯誤節點。

資料保證j是k的父親。

50<=p<=n<=700,m<=1500,T<=5。

資料保證每個正確節點均有至少2個兒子,至多3個兒子。

Output

T行每行一個實數表示每組資料的答案。請保留四位小數。

Sample Input

1
3 7 2
1 4
2 5
3 6
3 7

Sample Output

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