codeforces #343 E. Famil Door and Roads (最近公共祖先LCA+一点点概率)

题目:http://codeforces.com/contest/629/problem/E

题意:给定一棵n(n<100000)个节点的树,有m(m<100000)次查询,对于每次查询,给定两个顶点u和v,然后你要再添加一条边,使树上的两个顶点连接(构成一个环),并且u和v都在环内。求这个环的期望长度。

分析:

这题认真分析的话,其实很简单。

首先定义gx[cur]表示以cur为祖先其后代节点到cur的路径的长度之和。

定义gxall[cur]表示树上所有点到cur的路径的长度之和。

sz[cur]表示以cur为根的子树的大小。

分两种情况讨论:

①当u和v不是另外一个点的祖先。

假如以u为根的子树上的顶点集合为{a1,a2.....ax}

以v为根的子树上的顶点集合为{b1,b2.....by}

那么对于其中的一种组合的可能长度为dis(ai,u)+dis(bi,v)+dis(u,v)+1,其对答案的贡献为(dis(ai,u)+dis(bi,v)+dis(u,v)+1)/(x*y)。

那么期望长度为(y*gx[u]+x*gx[v])/(x*y)+dis(u,v)+1。

②当u是v的祖先或者v是u的祖先

同样的方法。只是涉及上面的点到当前点的长度和。根据gx[]推出gxall[]就行了。

代码:

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;
typedef unsigned long long ULL;
const LL INF = 1e9+7;
const LL MINT = ~0u>>1;

const int maxn = 200007;
const int LOG = 20;

struct node
{
	int v,next;	
}List[maxn];
int head[maxn],cnt;
int n,m;
void add(int u,int v)
{
	List[cnt].v=v;
	List[cnt].next=head[u];
	head[u]=cnt++;
}

int sz[maxn],anc[maxn][LOG],deep[maxn];
LL gx[maxn],gxall[maxn];

void Init()
{
	memset(head,-1,sizeof(head));
	cnt=0;
	for(int i=0;i<maxn;i++)
		for(int j=0;j<20;j++)
			anc[i][j]=1;
}

pair<int,LL> dfs(int cur,int dp)  //返回节点数和贡献 
{
	deep[cur]=dp;
	sz[cur]=1;
	for(int i=head[cur];~i;i=List[i].next)
	{
		int to = List[i].v;
		if(anc[cur][0]!=to)
		{
			anc[to][0]=cur;
			for(int j=1;j<LOG;j++)
				anc[to][j]=anc[anc[to][j-1]][j-1];
			pair<int,int> pii = dfs(to,dp+1);
			sz[cur]+=pii.first;
			gx[cur]+=pii.first+gx[to];
		}
	}
	return make_pair(sz[cur],gx[cur]);
}
void dfs2(int cur)
{
	int fa=anc[cur][0];
	//gxall[cur]=gxall[fa]-(gx[cur]+sz[cur])+(n-sz[cur])+gx[cur];
	if(cur!=1) 
		gxall[cur]=gxall[fa]+n-2*sz[cur]; //计算所有点到当前点的贡献 
	else
		gxall[cur]=gx[cur];
	for(int i=head[cur];~i;i=List[i].next)
		if(anc[cur][0]!=List[i].v)
			dfs2(List[i].v);
}
int Jump(int a,int x)
{
	for(int i=0;i<LOG;i++)
		if(x&(1<<i))
			a=anc[a][i];
	return a;
}
int Lca(int u,int v)
{
	if(deep[u]<deep[v])
		swap(u,v);
	u=Jump(u,deep[u]-deep[v]);
	if(u==v)	return u;
	for(int i=LOG-1;i>=0;i--)
	{
		if(anc[u][i]!=anc[v][i])
		{
			u=anc[u][i];
			v=anc[v][i];
		}
	}
	return anc[u][0];
}

void solve(int u,int v)
{
	int lca=Lca(u,v);
	if(lca!=u && lca!=v)
	{
		int base=deep[u]-deep[lca]+(deep[v]-deep[lca]);
		LL tn=sz[v];
		LL tm=sz[u];
		double ans=base;
		ans+=double(tn*gx[u]+tm*gx[v])/(tn*tm);
		printf("%.10lf\n",ans+1);
	}
	else
	{
		if(deep[u]<deep[v])
			swap(u,v);
		int base=deep[u]-deep[v];
		double ans=base;
		int ancSon=Jump(u,deep[u]-deep[v]-1);
		LL tn = sz[u];
		LL tm = n-sz[ancSon];
		LL gxu= gxall[lca]-(gx[ancSon]+sz[ancSon]);
		LL gxv= gx[u];
		ans+=double(tn*gxu+tm*gxv)/(tn*tm);
		printf("%.10lf\n",ans+1);
	}
}

int main()
{
	Init();
	scanf("%d%d",&n,&m);
	for(int i=1;i<n;i++)
	{
		int u,v;
		scanf("%d%d",&u,&v);
		add(u,v);
		add(v,u);
	}
	dfs(1,0);
	dfs2(1);
	while(m--)
	{
		int u,v;
		scanf("%d%d",&u,&v);
		solve(u,v);
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值