zoukankan      html  css  js  c++  java
  • 【BZOJ2878】[NOI2012] 迷失游乐园(基环树DP)

    点此看题面

    大致题意: 给定一棵树/基环树,随机一点作为起点,且每次随机走向一个没有走过的点,当旁边没有没走过的点时结束行走。每条边有一定边权,求走的总长度的期望。

    基环树

    应该是一道思维难度不高的题目,树的情况显然是非常套路的,而基环树的情况只要多讨论一个环上的转移即可。

    但是,思维难度不高,代码难度却高啊,至少我感觉我的做法非常复杂,调了半个下午才调出来。

    首先,对于环上每一个点,我们先遍历其子树求出一个(f_x)表示(x)子树内的贡献总和。

    然后,考虑在环上走的情况,显然从(i)走到(i+1)的概率是(frac{1}{deg_i-1})(i)不是起点,(i)(i-1)同理),但还要注意如果已经在环上绕了一圈,(i)的下一个点是起点,就不能接着在环上走了。

    所以在这一过程中只要特殊处理这最后一个点,并维护好其他所有点的贡献总和即可。

    至于其他的一些细节问题,由于调得很累,这里就懒得多说了,直接看代码吧。

    代码

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define N 100000
    #define DB long double
    #define add(x,y,v) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y,e[ee].val=v)
    #define swap(x,y) (x^=y^=x^=y)
    using namespace std;
    int n,m,ee,lnk[N+5];struct edge {int to,nxt,val;}e[2*N+5];
    class FastIO
    {
    	private:
    		#define FS 100000
    		#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
    		#define D isdigit(c=tc())
    		char c,*A,*B,FI[FS];
    	public:
    		I FastIO() {A=B=FI;}
    		Tp I void read(Ty& x) {x=0;W(!D);W(x=(x<<3)+(x<<1)+(c&15),D);}
    		Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
    		#undef D
    }F;
    class CircleTreeDP
    {
    	private:
    		int cnt,c[2*N+5],p[N+5],D[N+5];DB ans,f[N+5],s[N+5];
    		DB sum[2*N+5],F[2*N+5],pre[2*N+5],suf[2*N+5];
    		I int E(CI x,CI y) {for(RI i=lnk[x];i;i=e[i].nxt) if(e[i].to==y) return e[i].val;}
    		int vis[N+5],d[N+5],fa[N+5];I bool Find(CI x,CI lst=0)//找环
    		{
    			vis[x]=1;for(RI i=lnk[x],u,v;i;i=e[i].nxt) if((v=e[i].to)^lst)
    			{
    				if(!vis[v]) {if(d[v]=d[fa[v]=x]+1,Find(v,x)) return 1;continue;}
    				d[u=x]<d[v]&&swap(u,v);W(d[u]^d[v]) p[c[++cnt]=u]=1,u=fa[u];
    				RI w=v,t=0;W(p[c[++cnt]=u]=1,u^v) u=fa[u],v=fa[v],++t;
    				W(t) p[c[cnt+(t--)]=w]=1,w=fa[w];return 1;
    			}return 0;
    		}
    		I void DP(CI x,CI lst=0)//DP求出子树内的贡献
    		{
    			D[x]=1;for(RI i=lnk[x];i;i=e[i].nxt) e[i].to^lst&&!p[e[i].to]&&
    				(DP(e[i].to,x),f[x]+=(s[e[i].to]+=e[i].val),++D[x]);s[x]=D[x]^1?f[x]/(D[x]-1):0;
    		}
    		I void Calc(CI x,CI lst=0,DB t=0)//结合子树外的贡献统计答案
    		{
    			ans+=(f[x]+t)/D[x];for(RI i=lnk[x];i;i=e[i].nxt) e[i].to^lst&&
    				!p[e[i].to]&&(Calc(e[i].to,x,(D[x]^1?(f[x]+t-s[e[i].to])/(D[x]-1):0)+e[i].val),0);
    		}
    	public:
    		I void Tree() {DP(1),--D[1],Calc(1,0),printf("%.5Lf
    ",ans/n);}//处理树的情况
    		I void CTree()//处理基环树的情况
    		{
    			RI i;DB g,u;for(Find(1),i=1;i<=cnt;++i) DP(c[i]),++D[c[cnt+i]=c[i]];//对环上每个点先DP
    			for(pre[0]=i=1;i<=(cnt<<1);++i) pre[i]=pre[i-1]/(D[c[i]]-1);//统计前缀概率积
    			for(suf[cnt<<1|1]=1,i=(cnt<<1);i;--i) suf[i]=suf[i+1]/(D[c[i]]-1);//统计后缀概率积
    			for(F[1]=(D[c[1]]^2?f[c[1]]/(D[c[1]]-2):0),i=2;i<=(cnt<<1);++i)
    				sum[i]=sum[i-1]+E(c[i-1],c[i]),F[i]=(D[c[i]]^2?f[c[i]]/(D[c[i]]-2):0)+sum[i];
    			for(g=0,i=2;i^cnt;++i) g+=pre[i]*(D[c[1]]-1)*(D[c[i]]-2)*F[i];//预处理第1个点的答案
    			for(i=1;i<=cnt;++i) f[c[i]]+=g+pre[i+cnt-2]/pre[i]*F[i+cnt-1]-sum[i],//更新f
    				g=g*(D[c[i+1]]-1)+pre[i+cnt-1]/pre[i+1]*(D[c[i+cnt-1]]-2)*F[i+cnt-1]-(D[c[i+1]]-2)*F[i+1];//更新g
    			for(F[cnt<<1]-=sum[cnt<<1],sum[cnt<<1]=0,i=2*cnt-1;i;--i)
    				F[i]-=sum[i],sum[i]=sum[i+1]+E(c[i],c[i+1]),F[i]+=sum[i];
    			for(g=0,i=2*cnt-1;i^(cnt+1);--i) g+=suf[i]*(D[c[cnt<<1]]-1)*(D[c[i]]-2)*F[i];//预处理第n个点的答案
    			for(i=cnt<<1;i^cnt;--i) f[c[i]]+=g+suf[i-cnt+2]/suf[i]*F[i-cnt+1]-sum[i],//更新f
    				g=g*(D[c[i-1]]-1)+suf[i-cnt+1]/suf[i-1]*(D[c[i-cnt+1]]-2)*F[i-cnt+1]-(D[c[i-1]]-2)*F[i-1];//更新g
    			for(i=1;i<=cnt;++i) Calc(c[i]);printf("%.5Lf
    ",ans/n);//统计答案
    		}
    }T;
    int main()
    {
    	RI i,x,y,z;for(F.read(n,m),i=1;i<=m;++i) F.read(x,y,z),add(x,y,z),add(y,x,z);
    	return m^n?T.Tree():T.CTree(),0;
    }
    
  • 相关阅读:
    史上最全Java表单验证封装类
    QQ组件可导致IE10无响应
    如何获取特定用户组内的无效账户?
    IN2Windows 8 (Part 2)
    IN2Windows 8 (Part 4) 文件历史记录功能及其重置方法
    IN2Windows 8 (Part 3)
    Android 多文件监听的实现
    Android 调用打电话,发短信(彩信),发邮件,浏览器,分享,跳转系统的各个设置页面
    Android中Drawable小结
    Android 加载.gif格式图片
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/BZOJ2878.html
Copyright © 2011-2022 走看看