描述:
给一棵以 1 为根的有根树,你希望走到某个叶子节点。在非叶子节点 ,你下一步 有 的概率回到根节点 ,也可能随机向相邻节点走一步:具体地,设 有 个儿子 ,有 的概率走到 ;当 不是根节点 时,有 的概率走到 的父亲节点 。求从根节点 走到叶子节点的期望步数。
思路:
这道题可以先设状态:设 为从 点走向叶子节点的期望步数。
这里先令 为 ,即从点 向下走的概率。这里 为 的儿子。
根据题目的定义,我们可以得到:
然后进一步拆解这个式子得:
特别地、,。
于是,我们将所有的 都转化为了 的形式(此处 为常数)。然后我们就可以用高斯消元做了。
……
Over 了吗?高斯消元的算法复杂度是 ,你 过 是肯定不现实的。于是我们应该尝试优化这个算法。
观察这个式子可以知道,这些 是可以从叶子节点一步一步推上来的。因为叶子节点的 值都为 。
为了使思路更清晰,先列一下式子:
然后把 式子带入 式子得:
于是我们就得出了 关于 的转移式子。
记住在当 的时候最后要将 。因为最后 里面没有 。
于是最后我们就得到了形如 的式子。显然地、。于是这道题就做完了。
总结:
- 做期望的题首先是设状态。状态一般是分两种:
- 然后再列转移式子。这一步千万不要吝惜推式子!(就像这道题一样)但是也别方向都找错了在这推式子。
- 然后就会得到一个最终式子,这个式子一般是基于其它的状态。如果没有后效性,一般就用动态规划 DP。而如果有后效性,就用有后效性 DP 或者高斯消元法。(当然这道题其实是对高斯消元法的优化)
这道题重点在设状态和推式子,代码很短:
Code (1.05 KiB):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| #include<bits/stdc++.h> using namespace std; const int N = 1e5+5; struct Edge{ int to, nxt; }edge[N<<1]; int head[N], cnt; void add(int u, int v){ edge[++cnt] = {v, head[u]}; head[u] = cnt; } double A[N], B[N], C[N], h[N], q[N]; int n, fa[N]; void dfs(int x){ int soncnt = 0; for(int i=head[x];i;i=edge[i].nxt){ int v = edge[i].to; dfs(v); soncnt ++; } if(soncnt == 0) return ; double D = 1; for(int i=head[x];i;i=edge[i].nxt){ int v = edge[i].to; A[x] += q[v] * A[v]; B[x] += q[v]; C[x] += q[v] * C[v]; D -= q[v] * B[v]; } A[x] += h[x], B[x] = 1-h[x]-B[x], C[x] += 1; A[x] /= D, B[x] /= D, C[x] /= D; if(fa[x] == 1) A[x] += B[x], B[x] = 0; } int main(){ cin>>n; for(int i=2;i<=n;i++){ cin>>fa[i]; add(fa[i], i); } for(int i=2;i<=n;i++) cin>>q[i]; for(int i=1;i<=n;i++) cin>>h[i]; dfs(1); double ans = C[1] / (1-A[1]); printf("%.2lf", ans); return 0; }
|