【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\n",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\n",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;
}
posted @ 2020-06-15 15:55  TheLostWeak  阅读(15)  评论(0编辑  收藏