51nod - 1681 公共祖先

有一个庞大的家族,共n人。已知这n个人的祖辈关系正好形成树形结构(即父亲向儿子连边)。

在另一个未知的平行宇宙,这n人的祖辈关系仍然是树形结构,但他们相互之间的关系却完全不同了,原来的祖先可能变成了后代,后代变成的同辈……

两个人的亲密度定义为在这两个平行宇宙有多少人一直是他们的公共祖先。

整个家族的亲密度定义为任意两个人亲密度的总和。

Input

第一行一个数n(1<=n<=100000)
接下来n-1行每行两个数x,y表示在第一个平行宇宙x是y的父亲。
接下来n-1行每行两个数x,y表示在第二个平行宇宙x是y的父亲。

Output

一个数,表示整个家族的亲密度。

Input示例

5
1 3
3 5
5 4
4 2
1 2
1 3
3 4
1 5

Output示例

6

思路:

利用dfs统计第一个宇宙中的父子关系的起点和终点,al[i]里记录节点i在第一次dfs中的位置。在第二个宇宙中继续利用dfs,根据新的父子关系添加节点。最后利用排列组合从n个节点任意取出两个来计算。统计和时利用树状数组。

#include<iostream>  
#include<cstring>  
using namespace std;

typedef long long ll; 
const int MAXN = 1e5 + 1;
int n, x, y, fi[MAXN], w[2 * MAXN], ne[2 * MAXN], cnt, al[MAXN], ar[MAXN], root;  
ll ans, c[MAXN];  
bool isChild[MAXN];

void add(int u, int v)  
{  
    w[++cnt] = v;
	ne[cnt] = fi[u];
	fi[u] = cnt;  

    w[++cnt] = u;
	ne[cnt] = fi[v];
	fi[v] = cnt;  
}  

int lowbit(int x)
{
	return x & (-x);
}

void add1(int x)  
{  
	while (x <= n + 1)
	{
		c[x]++;
		x += lowbit(x);
	}
}  
  
int getSum(int x)  
{  
	ll result = 0;
	while (x > 0)
	{
		result += c[x];
		x -= lowbit(x);
	}

	return result;
}  
   
      
void dfs1(int u, int fa)  
{  
    al[u] = ++cnt;  
    for (int i = fi[u]; i; i = ne[i]) 
	{
		if (w[i] != fa) 
		{
			dfs1(w[i], u);  
		}
	}
    ar[u] = cnt;  
}  
      
void dfs2(int u, int fa)  
{  
    ll now = getSum(ar[u]) - getSum(al[u]-1);
	add1(al[u]);  
    for (int i = fi[u]; i; i = ne[i]) 
	{
		if (w[i] != fa) 
		{
			dfs2(w[i], u);  
		}
	}
    now = getSum(ar[u]) - getSum(al[u] - 1) - now - 1;  
    ans += now * (now - 1) / 2;  
}  
      
int main()  
{  
	cin >> n;
	memset(isChild, false, sizeof(isChild));

    for (int i = 1; i < n; i++) 
	{
		cin >> x >> y;
		isChild[y] = true;
		add(x, y);  
	}

    for (root = 1; isChild[root]; root++);
	cnt = 0;
	dfs1(root, 0);  
    memset(fi, 0, sizeof(fi));  
	memset(isChild, false, sizeof(isChild));
	cnt = 0;  

    for (int i = 1; i < n; i++) 
	{
		cin >> x >> y;
		isChild[y] = true;
		add(x, y);  
	}
    for (root = 1; isChild[root]; root++);
	cnt = 0;
	dfs2(root, 0);  
	cout << ans << endl;

    return 0;  
}  

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值