Luogu2081 「NOI2012」 迷失遊樂園
阿新 • • 發佈:2020-09-12
Description
在一棵基環樹(或者樹)任選點,不能走走過的點,求走到不能走的期望長度
\(n\le 10^5\),環上的點 \(\le 20\)
Solution
樹的部分可以考慮用換根法來解決
定義 \(f_i\) 為以 \(i\) 為起點,在子樹裡面的期望路徑長度
這裡有一個小誤區:這個定義轉移的時候應該除以 \(du_i-1\) (狀態定義)
正確性用期望線性效能給
另一個部分用換根做就行
struct task1{ int n, m, cnt=1, du[N], head[N]; double f[N], g[N], ans, d[N]; struct node { int to, dis, nxt; } e[N << 1]; inline void add(int u, int v, int w) { e[++cnt].to = v; ++du[v]; e[cnt].nxt = head[u]; e[cnt].dis = w; return head[u] = cnt, void(); } inline void dfs1(int x, int fa) { for (reg int i = head[x]; i; i = e[i].nxt) { int t = e[i].to; if (t == fa) continue; dfs1(t, x); d[x] += f[t] + e[i].dis; } f[x] = d[x] / max(1,du[x]-1); return; } inline void dfs2(int x, int fa) { for (reg int i = head[x]; i; i = e[i].nxt) { int t = e[i].to; if (t == fa) continue; d[t] += (d[x] - f[t] - e[i].dis) / max(1ll, du[x] - 1) + e[i].dis; dfs2(t, x); } return; } inline void main() { for(reg int i=1,u,v,w;i<=m;++i) u = read(), v = read(), w = read(), add(u, v, w), add(v, u, w); dfs1(1, 0); dfs2(1, 0); For(i, 1, n) ans += 1.0 * d[i] / du[i]; printf("%.6lf\n", ans / n); return ; } }T1;
然後考慮環套樹的情況
基於 \(Island\) 和 \(Roads\ In \ The \ Kindom\) 中的套路:
把一棵基環樹看成當前子樹的根,然後分別處理掉,再對環上的點處理一下
先算出來 \(f_i\) ,然後考慮環上的點的 \(g_i\) 值,可以往左側走,也可以往右側走
可以暴力處理環上點的 \(g_i\) 值,然後再反向更新回去子樹裡面的 \(g_i\)
(我實現能力可能是真的不行,寫了一週,調了好久好久)
struct task2{ int cnt,rt, du[N], head[N]; double t[N<<1],f[N], g[N], ans,res[N], d[N]; struct node { int to, dis, nxt; } e[N << 1]; inline void add(int u, int v, int w) { e[++cnt].to = v; e[cnt].nxt = head[u]; e[cnt].dis = w; return head[u] = cnt, void(); } pair<int,int> h[N<<1]; int num,vis[N]; inline bool find(int x,int fa,int pre) { if(vis[x]==1) { h[++num]=make_pair(x,e[pre].dis); vis[x]=3; return 1; } vis[x]=1; for(int i=head[x];i;i=e[i].nxt) { if(i==(pre^1)) continue; int t=e[i].to; if(find(t,x,i)) { if(vis[t]==3&&num!=1) return 0; if(vis[x]!=3) h[++num]=make_pair(x,e[pre].dis); return 1; } if(num) return 0; }return 0; } inline void dfs1(int x,int fa) { for(reg int i=head[x];i;i=e[i].nxt) { int t=e[i].to; if(t==fa||vis[t]) continue; dfs1(t,x); d[x]+=f[t]+e[i].dis; du[x]++; } if(du[x]) f[x]=d[x]/du[x]; if(x!=rt) du[x]++; return ; } inline void dfs2(int x,int fa) { for(reg int i=head[x];i;i=e[i].nxt) { int t=e[i].to; if(t==fa||vis[t]) continue; double tmp=(res[x]*du[x]-e[i].dis-f[t])/max(1,du[x]-1); res[t]=(f[t]*(du[t]-1)+tmp+e[i].dis)/du[t]; dfs2(t,x); } return ; } inline void main() { cnt=1; for(reg int i=1,u,v,w;i<=m;++i) u = read(), v = read(), w = read(), add(u, v, w), add(v, u, w); find(1,0,0); memset(vis,0,sizeof(vis)); For(i,1,num) vis[h[i].first]=1; For(i,1,num) h[i+num]=h[i],rt=h[i].first,dfs1(h[i].first,0); For(i,1,num) { int nd=h[i].second; double np=1; For(j,i+1,num+i-1) { if(j!=num+i-1) g[h[i].first]+=(nd+f[h[j].first]*du[h[j].first]/(du[h[j].first]+1))*np; else g[h[i].first]+=(nd+f[h[j].first])*np; nd=h[j].second; np/=(du[h[j].first]+1); } nd=h[i+num-1].second; np=1; Down(j,i+1,num+i-1) { if(j!=i+1) g[h[i].first]+=(nd+f[h[j].first]*du[h[j].first]/(du[h[j].first]+1))*np; else g[h[i].first]+=(nd+f[h[j].first])*np; nd=h[j-1].second; np/=(du[h[j].first]+1); } } For(i,1,num) res[h[i].first]=(f[h[i].first]*du[h[i].first]+g[h[i].first])/(du[h[i].first]+2),du[h[i].first]+=2,dfs2(h[i].first,0); For(i,1,n) ans+=res[i]; printf("%.6lf\n",ans/n); return ; } }T2;
Review
\(1.\) 本題的最大收穫就是 \(g_i\) 的轉移一定要想清楚再寫,改來改去浪費時間
其實環的處理思路並不複雜,要不是說我把 \(g_i\) 想亂了,就是個水題……
\(2.\) 更新自己找環的方法:
inline bool find(int x,int fa,int pre) { if(vis[x]==1) { h[++num]=make_pair(x,e[pre].dis); vis[x]=3; return 1; } vis[x]=1; for(int i=head[x];i;i=e[i].nxt) { if(i==(pre^1)) continue; int t=e[i].to; if(find(t,x,i)) { if(vis[t]==3&&num!=1) return 0; if(vis[x]!=3) h[++num]=make_pair(x,e[pre].dis); return 1; } if(num) return 0; }return 0; }
改的地方主要是判斷了個 \(vis[t]==3\) 否則在祖先那裡會錯……
\(3.\) 正確學習期望線性性,這裡主要是在暴力處理環那部分
期望長度應該是每個邊乘上概率加和,而不是路徑長度乘上概率……
(想出來環的那個可以 \(O(len^2)\) 做就真的不難……)