[NOI2012]迷失游乐园

枫林晚

2018-09-05 15:20:19

Solution

安利cnblog博客:[[Noi2012]迷失游乐园](https://www.cnblogs.com/Miracevin/p/9592243.html) 比较麻烦的图论期望题。 花费3hWA一次,然后AC了此题。 和网上的题解大致差不多。肯定也要分树形和基环树两种情况讨论。 ### 首先考虑树的: 本人没有用什么up数组。只有一个E[x],P[x] 表示,从x往下走的期望长度。到点X的概率。 树 对于一棵树,直接dfs1,找到以1为根的信息。 然后换根即可。dfs2就是换根、。 ans[x]={ len[fa]+(ans[fa]-(E[x]/P[x]+len[fa])×1/d[fa])×d[fa]/(d[fa]-1) }×1/d[x] + E[x]/P[x] × (d[x]-1)/d[x] len[fa]表示,x到fa的边的长度。ans[i]表示,从i出发的期望长度。 貌似非常复杂。因为少记一个up嘛。 就是,算了两个部分,x向上走到fa的贡献,和向下走到儿子的贡献 前半部分向上走的: 算ans[fa]抛去x的部分:E[x]/P[x],把x向下走的贡献变成P[x]=1的贡献,然后加上len[fa],再乘上fa到x的概率,就是1/d[fa] 减掉之后,由于其他部分,从ans[x]出发,每个点的概率是1/d[fa]的,但是,现在fa不是根,变成了1/(d[fa]-1) 所以,后面乘了一个d[fa]/(d[fa]-1),就变成了概率为1/(d[fa]-1)的情况。 再加上一个len[fa],就是如果走到fa的贡献了。当然还要有一个概率1/d[x] 后半部分向下走的: E[x]/P[x],把从x出发的概率变成1,由于现在到每一个儿子的概率不是1/(d[x]-1)了,可以往上走,就变成了1/d[x] 也要调整一下。 就可以换根啦! ### 基环树:   环比较麻烦。从下面的树往上走不好考虑。   所以先处理环。 用一个栈dfs3找到所有的环上的点。是环上的点,记录进huan,共tot个。rac[i]=1,表示i是环上的点。 然后从这些环上点暴力dp,20n也不会TLE,找到ans[huan[i]] 环上暴力dp进入wrk函数。 钦定出发点st不能访问。发现,要么往左环上走,要么往右环上走,要么往下面的树走。 往树走直接dfs1即可。 往环走,发现,不经过st的情况下,也是遍历一个树诶!!!,只不过是“横躺”了环上的一部分 所以仍然可以dfs1处理。 然后,再对每一个环的每一个子树一遍dfs1,就得到了从这个子树的根(环点的一个儿子),往下走的E[x]了。 然后,对于每一个环上节点,分别往下换根。开始的时候,fa就是环上节点。 这是dfs4(其实和dfs2差不多) 换根的转移方程类似了。(不过,代码中是exp,传进来的时候其实就是ans[fa]) 两个值得注意的细节: 1.dfs3找环的时候,第一次vis[y]就fl=true之后vis[y]都不可能再是环了。(可能不是正经的找基环的方法) 2.dfs1的时候,为了确定儿子的选择方法,可以暴力算一下,方便之后的转移概率处理。 ## Code ```cpp #include<bits/stdc++.h> using namespace std; const int N=100000+5; int n,m; struct node{ int nxt,to,val; }e[2*N]; int hd[N],cnt; double E[N],P[N]; double ans[N]; double op; int d[N]; void add(int x,int y,int z){ e[++cnt].nxt=hd[x]; e[cnt].to=y; e[cnt].val=z; hd[x]=cnt; } bool vis[N]; int rt; void dfs1(int x,double to,int fa,int dis){//算子树期望 ,to是到这个点的概率,dis其实没用 E[x]=0.00; P[x]=to; double lv=0.0; int can=0; for(int i=hd[x];i;i=e[i].nxt){//注意这里,先暴力算一下能走几个儿子,避免概率算错。 int y=e[i].to; if(y==fa) continue; if(vis[y]) continue; can++; } if(can!=0) lv=1.00/((double)can);//走每个儿子的概率,不要除以0 for(int i=hd[x];i;i=e[i].nxt){ int y=e[i].to; if(y==fa) continue; if(vis[y]) continue; dfs1(y,to*lv,x,dis+e[i].val); E[x]+=P[y]*(double)e[i].val+E[y];//注意,P[y]不要再乘E[y]了,传进去的时候,to*lv相当于已经乘过了 } } void dfs2(int x,double exp,int fa,int val){ if(x!=1){//麻烦的换根方程式,注意不要除以0 if(d[fa]!=1) exp=((double)val+(exp-(E[x]/P[x]+(double)val)*(1.00/(double)d[fa]))*((double)d[fa]/((double)d[fa]-1.00)))*(1.00/(double)d[x]); else exp=(double)val*(1.00/(double)d[x]); exp+=((double)E[x]/(double)P[x])*(((double)d[x]-1.00)/((double) d[x])); ans[x]=exp; } for(int i=hd[x];i;i=e[i].nxt){ int y=e[i].to; if(y==fa) continue; dfs2(y,exp,x,e[i].val); } } int rac[N]; int huan[N],tot; int sta[N],top; bool fl=false; void dfs3(int x,int fa){ sta[++top]=x; vis[x]=1; for(int i=hd[x];i;i=e[i].nxt){ int y=e[i].to; if(y==fa) continue; if(vis[y]){ if(fl) continue; fl=true;//注意这个fl,保证第一次找到vis的情况(也就是环),就不会再进行了,因为回溯的时候,会再vis[y]==1一次 int z; do{ z=sta[top]; rac[z]=1; huan[++tot]=z; top--; }while(z!=y); } else dfs3(y,x); } if(sta[top]==x) top--; } void dfs4(int x,double exp,int fa,int val){//类似的基环树换根 if(d[fa]!=1) exp=((double)val+(exp-(E[x]/P[x]+(double)val)*(1.00/(double)d[fa]))*((double)d[fa]/((double)d[fa]-1.00)))*(1.00/(double)d[x]); else exp=(double)val*(1.00/(double)d[x]); exp+=((double)E[x]/(double)P[x])*(((double)d[x]-1.00)/((double) d[x])); ans[x]=exp; for(int i=hd[x];i;i=e[i].nxt){ int y=e[i].to; if(y==fa) continue; dfs4(y,ans[x],x,e[i].val); } } double wrk(int st){//计算基环树环点的ans memset(E,0,sizeof E); vis[st]=1; double ret=0.0; rt=0; if(rac[st]){ for(int i=hd[st];i;i=e[i].nxt){ int y=e[i].to; if(rac[y]){ dfs1(y,1.00/(double)d[st],st,e[i].val); ret+=P[y]*e[i].val+E[y]; } else{ dfs1(y,1.00/(double)d[st],st,e[i].val); ret+=P[y]*e[i].val+E[y]; } } } vis[st]=0; return ret; } int main() { scanf("%d%d",&n,&m);int x,y,z; for(int i=1;i<=m;i++){ scanf("%d%d%d",&x,&y,&z); add(x,y,z);add(y,x,z); d[x]++,d[y]++; } if(m==n-1){//是树 rt=1; dfs1(1,1.00,0,0); ans[1]=E[1]; dfs2(1,E[1],0,0); op=0; for(int i=1;i<=n;i++){ op+=(1.00/(double)n)*ans[i]; } printf("%.5lf",op); return 0; } else{//是基环树 dfs3(1,0); memset(vis,0,sizeof vis); for(int i=1;i<=tot;i++){ ans[huan[i]]=wrk(huan[i]); } memset(vis,0,sizeof vis); for(int j=1;j<=tot;j++){ int x=huan[j]; vis[x]=1; for(int i=hd[x];i;i=e[i].nxt){ int y=e[i].to; if(rac[y]) continue; dfs1(y,1.00,x,0); } } for(int j=1;j<=tot;j++){ int x=huan[j]; vis[x]=1; for(int i=hd[x];i;i=e[i].nxt){ int y=e[i].to; if(rac[y]) continue; dfs4(y,ans[x],x,e[i].val); } } for(int i=1;i<=n;i++){ op+=(1.00/(double)n)*ans[i]; } printf("%.5lf",op); return 0; } } ```