倍增法求LCA -- HihoCoder 1167 Advanced Theoretical Computer Science

#1167 : Advanced Theoretical Computer Science

Time Limit: 20000ms
Case Time Limit: 1000ms
Memory Limit: 256MB

Description

Yuuka is learning advanced theoretical computer science these days.But she cannot understand it. So she decided to hang out to relax.
Outside the door there is a tree of n nodes. She found there are P fairies on the tree. The fairies are very nervous, so each fairy only shows on a particular route on the tree. To be more specific, the i-th fairy will show on the path between nodes ai and bi.
Two fairies are friends if there route have some (no less than one) points in common.
Yuuka wants to know that how many pairs of fairies a,b (a!=b) are friends. Note that a,b and b,a are considered to be the same.

Input

The first line with two integers n and P (1 <= n, P <= 100000), meaning the size of the tree and the number of fairies. The nodes in the tree are numbered from 1 to n.
Then following n-1 lines with two integers a and b on each line, meaning there is an edge between a and b (a!=b).
Then following P lines, each line with two integers ai and bi, meaning the i-th fairy will show on the path from ai to bi. (ai!=bi)

Output

One line with an integer representing the answer.

Sample Input
6 3
1 2
2 3
2 4
4 5
4 6
1 3
1 5
5 6
Sample Output
2

题目的大意就是在一棵以1为根树上有若干个仙子。每个仙子都会在两个点之间游荡。如果两个仙子的游荡路径有重复,那么这两个仙子就是一对儿朋友。求总共有多少对朋友。


树 + 两点间路径,不难想到LCA。但是我们要如何记录朋友的数量呢?


经过观察不难发现:两个仙子互为朋友的充分必要条件是,其中一个仙子路径的LCA在另一个仙子的路径上。那么我们能否记录下每个节点作为LCA出现的次数,然后遍历每个仙子来积累朋友数量呢?


显然不行。因为很有可能两个仙子的LCA重合。在这样的情况下我们会把朋友数量算多,而且遍历的过程在数据量这么大的情况下铁定崩溃。既然蛋疼在重复计算上,或许我们可以分开讨论?


对于LCA重合的仙子们的朋友数量很容易求:设self[i]表示的是第i号节点作为LCA的次数。那么这种朋友的数目总和就是[sigma(1,n)](self[i] * (self[i] - 1]) / 2)。

(第一次写博客不知道那个求和的sigma咋打出来,难看了点,见谅!)


简单的情况考虑完了,现在我们要计算“LCA在路径上,且两个LCA不能重合”的朋友对数(我们称之为B类朋友)。如果定义sum[i]表示第i号节点,及其所有长辈节点的self[i]的数量和,那么对于一个穿梭于a和b之间的仙子来说,和它互为B类朋友的人数为sum[a] + sum[b] - 2 * sum[LCA[a][b]] 。 LCA那里被计算了两次,因此要全部减出去。


写到这里,估计有朋友会问:那又何必分成两类?直接把所有朋友都当作B类朋友,然后在计算的时候减去一倍的sum[LCA[a][b]]不就完了么?


其实我第一次就是这么搞的。幸亏没过样例- -如果这样的话,假设a和b两个仙子的LCA相同。在计算a的朋友的时候我们把b算进去了。在计算b的时候不是又把a也给算进去了吗?所以说,LCA相同的情况本身就是一个蛋疼的情况,需要特殊考虑一下。


#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<list>
#include<vector>
#include<stdlib.h>
#define N 100005
#define LOGN 20
using namespace std;

vector<int>e[N];
int sum[N],self[N],parent[LOGN][N],depth[N],n,m,p;
long long int ans = 0;//要注意数据范围。因为没用long long我还哇了一发。
pair<int,int>f[N];//ÏÉ×Ó 

/***************LCA**************/

void dfs(int v,int p,int d)
{
	parent[0][v] = p;
	depth[v] = d;
	for(int i = 0;i<e[v].size();i++)
	if(e[v][i] != p)dfs(e[v][i],v,d+1);
}

int lca(int u,int v)
{
	if(depth[u] > depth[v])swap(u,v);
	for(int k = 0; k < LOGN;k++)
	if(depth[v] - depth[u] >> k & 1)
	v = parent[k][v];
	if(u == v)
	return u;
	for(int k = LOGN - 1;k >= 0;k--)
	if(parent[k][u] != parent[k][v])
	u = parent[k][u],v = parent[k][v];
	return parent[0][u];
}

/***************LCA**************/

void getSum(int v,int p)
{
	sum[v] = self[v] + sum[p];
	for(int i = 0;i<e[v].size();i++)
	if (e[v][i] != p) getSum(e[v][i],v);
}

void init()
{
	int root = 1;
	int t1,t2;
	for(int i = 1;i<=m;i++)
	{
		cin>>t1>>t2;
		e[t1].push_back(t2);
		e[t2].push_back(t1);
	}
	dfs(root,-1,0);
	for(int k = 0; k+ 1 < LOGN;k++)
	for(int v = 1; v <= n; v++)
	if(parent[k][v] < 0)parent[k+1][v] = -1;
	else parent[k+1][v] = parent[k][parent[k][v]];
	for(int i = 1;i<=p;i++)
	{
		cin>>f[i].first>>f[i].second;
		int h = lca(f[i].first,f[i].second);
		self[h]++;
	}
	getSum(root,0);
}

void work()
{
	for(int i = 1;i<=p;i++)
		ans += sum[f[i].first] + sum[f[i].second] - 2 * sum[lca(f[i].first,f[i].second)];
	for(int i = 1;i<=n;i++)
		ans += (long long int)self[i] * (long long int)(self[i] - 1) / 2;
}

int main()
{
	cin>>n>>p;
	m = n - 1;//懒得改模板了,表喷我
	init();
	work();
	cout<<ans<<endl;
	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值