『LCA·树形DP换根』CF629E Famil Door and Roads

P r o b l e m \mathrm{Problem} Problem

Famil Door’s City map looks like a tree (undirected connected acyclic graph) so other people call it Treeland. There are n n n intersections in the city connected by n − 1 n-1 n1 bidirectional roads.

There are m m m friends of Famil Door living in the city. The i i i -th friend lives at the intersection u i u_{i} ui and works at the intersection v i v_{i} vi . Everyone in the city is unhappy because there is exactly one simple path between their home and work.

Famil Door plans to construct exactly one new road and he will randomly choose one among n ⋅ ( n − 1 ) / 2 n·(n-1)/2 n(n1)/2 possibilities. Note, that he may even build a new road between two cities that are already connected by one.

He knows, that each of his friends will become happy, if after Famil Door constructs a new road there is a path from this friend home to work and back that doesn't visit the same road twice. Formally, there is a simple cycle containing both u i u_{i} ui and v i v_{i} vi .

Moreover, if the friend becomes happy, his pleasure is equal to the length of such path (it's easy to see that it's unique). For each of his friends Famil Door wants to know his expected pleasure, that is the expected length of the cycle containing both u i u_{i} ui and$ v_{i}$ if we consider only cases when such a cycle exists.


T r a n s l a t i o n \mathrm{Translation} Translation

给出一棵 n n n 个节点的树。

m m m 个询问,每一个询问包含两个数 a 、 b a、b ab

我们可以对任意两个不相连的点连一条无向边,

并且使得加上这条边后 a , b a,b ab 处在一个环内。

对于每一个询问,求这样的环的期望长度。


S o l u t i o n \mathrm{Solution} Solution

这道题我们需要分类讨论。

当询问节点 u u u v v v 无祖先关系时。
在这里插入图片描述
我们在两个节点的子树中个选择一个点即能构成环。

那个方案数为 s i z e u × s i z e v \mathrm{size_u}\times \mathrm{size_v} sizeu×sizev,那么我们包含的答案为: ( d i s ( u , v ) + 1 ) × s i z e x × s i z e v (\mathrm{dis}(u,v)+1)\times \mathrm{size_x} \times \mathrm{size_v} (dis(u,v)+1)×sizex×sizev

我们考虑还有哪些贡献,发现每一个子树内的节点到根节点的距离被计算了另一个子树的大小次,因此我们根据每一个节点的贡献,我们可以得到如下贡献:

s i z e y ∑ x ∈ s o n u d i s x + s i z e x ∑ x ∈ s o n v d i s x \mathrm{size_y}\sum_{x∈son_u}dis_x+\mathrm{size_x}\sum_{x∈son_v}dis_x sizeyxsonudisx+sizexxsonvdisx

我们现在考虑如何计算出 f x = ∑ i ∈ s o n ( x ) d i s i f_x=\sum_{i∈son(x)}dis_i fx=ison(x)disi,很容易发现有状态转移方程:

f x = ∑ ( f y + s i z e y ) f_x=\sum (f_y+\mathrm{size_y}) fx=(fy+sizey)


u u u v v v 存在祖先关系时(假设 u u u v v v 的祖先)
在这里插入图片描述
u u u选择上面以及不是 v v v所在子树的任意节点。 v v v选择子树。

l e s s less less v v v u u u 路径中的倒数第二个点。则方案数可以表示为:
c n t = ( n − s i z e l e s s ) × s i z e v \mathrm{cnt}=(n-\mathrm{size_{less}})\times \mathrm{size_v} cnt=(nsizeless)×sizev
而且我们 u u u上面和其他子树的深度贡献,我们可以设 g x g_x gx表示所有节点到 x x x的深度之和,这是树形DP换根的基础操作,再用 g x − s i z e y − f y g_x-\mathrm{size_y}-f_y gxsizeyfy即为答案。

最后泪乘即可。


C o d e \mathrm{Code} Code

#include <bits/stdc++.h>
#define int long long
#define LL long long

using namespace std;
const int N = 2e5;

int n, m;
int fa[N], dep[N], top[N], size[N], son[N], f[N], g[N], up[N][30];
vector < int > a[N];

int read(void)
{
	int s = 0, w = 0; char c = getchar();
	while (c < '0' || c > '9') w |= c == '-', c = getchar();
	while (c >= '0' && c <= '9') s = s*10+c-48, c = getchar();
	return w ? -s : s;
}

void Dfs1(int x, int father)
{
	int Max = 0;
	size[x] = 1;
	fa[x] = up[x][0] = father;
	dep[x] = dep[father] + 1;
	for (int i=0;i<a[x].size();++i)
	{
		int y = a[x][i];
		if (y == father) continue;
		Dfs1(y, x);
		size[x] += size[y];
		f[x] += f[y] + size[y];
		if (size[y] > Max) Max = size[son[x] = y];
	}
	return;
}

void Dfs2(int x,int cur)
{
	top[x] = cur;
	if (son[x]) {
		g[son[x]] = g[x] - size[son[x]] + (n - size[son[x]]);
		Dfs2(son[x], cur);
	}
	for (int i=0;i<a[x].size();++i)
	{
		int y = a[x][i];
		if (y == fa[x] || y == son[x]) continue;
		g[y] = g[x] - size[y] + (n - size[y]);
		Dfs2(y, y);
	}
	return;
}

int LCA(int x, int y)
{
	while (top[x] ^ top[y])
	{
		if (dep[top[x]] < dep[top[y]]) swap(x, y);
		x = fa[top[x]];
	}
	return dep[x] < dep[y] ? x : y;
}

void DP(void)
{
	for (int j=1;j<=20;++j)
		for (int i=1;i<=n;++i)
			up[i][j] = up[up[i][j-1]][j-1];
	return;
}

int Get(int u,int v)
{
	for (int i=20;i>=0;--i)
	{
		if (up[u][i] == 0) continue;
		if (dep[up[u][i]] > dep[v]) u = up[u][i];
	}
	return u;
}

void work(void)
{
	int u = read(), v = read();
	int P = LCA(u, v);
	if (u != P and v != P)
	{
		LL cnt = 1LL * size[u] * size[v];
		LL sum = 1LL * size[v] * f[u] + 1LL * size[u] * f[v];
		sum += cnt * (dep[u] + dep[v] - 2 * dep[P] + 1);
		printf("%.8lf\n", 1.0 * sum / cnt);
		return;
	}
	if (dep[u] < dep[v]) swap(u, v);
	int less = Get(u, v);
	LL cnt = 1LL * size[u] * (n - size[less]);
	LL sum = 1LL * (n - size[less]) * f[u] + size[u] * (g[v] - f[less] - size[less]);
	sum += 1LL * cnt * (dep[u] - dep[v] + 1);
	printf("%.8lf\n", 1.0 * sum / cnt);
	return;
}

signed main(void)
{
	n = read(), m = read();
	for (int i=1;i<n;++i)
	{
		int x = read(), y = read();
		a[x].push_back(y);
		a[y].push_back(x);
	}
	Dfs1(1, 0), g[1] = f[1];
	Dfs2(1, 1), DP();
	while (m --) work(); 
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值