1681 公共祖先
Description
有一个庞大的家族,共n人。已知这n个人的祖辈关系正好形成树形结构(即父亲向儿子连边)。
在另一个未知的平行宇宙,这n人的祖辈关系仍然是树形结构,但他们相互之间的关系却完全不同了,原来的祖先可能变成了后代,后代变成的同辈……
两个人的亲密度定义为在这两个平行宇宙有多少人一直是他们的公共祖先。
整个家族的亲密度定义为任意两个人亲密度的总和。
Input
第一行一个数n(1<=n<=100000)
接下来n-1行每行两个数x,y表示在第一个平行宇宙x是y的父亲。
接下来n-1行每行两个数x,y表示在第二个平行宇宙x是y的父亲。
Output
一个数,表示整个家族的亲密度。
Input example
5
1 3
3 5
5 4
4 2
1 2
1 3
3 4
1 5
Output example
6
Solution
很神奇的数据结构题,首先,我们先dfs便历第一棵树,记录每个点的入时间戳和出时间戳(这点和tarjan类似),对于第二棵树,我们也进行一遍dfs,对于每一个节点u,我们先记录在第二棵树上,不包含u这个节点的子树时,有多少节点在u所包含的时间戳里出现过,因为出现过就知道这个点所覆盖的时间戳一定不是以u为祖先了,之后我们来便历第二棵树中u的子树,并在便历完之后重新统计有多少个节点在u所包含的时间戳里出现过,两次答案相减,便是两棵子树中都以u为祖先的节点数,设这个差为sum,则这个节点u对答案的贡献为(sum-1)*(sum-2),对于统计u所包含的时间戳中的结点个数,我们可以用树状数组来维护,代码如下:
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
vector <int> edge[100010];
vector <int> pic[100010];
long long tree[10000000];
struct node{
int in,out;
}dfn[100010];
int cnt,In[100010],n;
long long ans;
int lowbit(int x){
return x&(-x);
}
void dfs1(int u,int fa){
dfn[u].in=++cnt;
for (int i=0;i<edge[u].size();i++){
int v=edge[u][i];
if (v==fa) continue;
dfs1(v,u);
}
dfn[u].out=cnt;
}
long long query(int x){
long long ans=0;
for (int i=x;i;i-=lowbit(i)){
ans+=tree[i];
}
return ans;
}
void add(int x,int val){
for (int i=x;i<=n;i+=lowbit(i)){
tree[i]+=val;
}
}
void dfs2(int u,int fa){
long long sum=query(dfn[u].out)-query(dfn[u].in-1);
add(dfn[u].in,1);
for (int i=0;i<pic[u].size();i++){
int v=pic[u][i];
if (v==fa) continue;
dfs2(v,u);
}
sum=query(dfn[u].out)-query(dfn[u].in-1)-sum-1;
ans+=(sum-1)*sum/2;
}
int main(){
scanf("%d",&n);
for (int i=1;i<n;i++){
int u,v;
scanf("%d%d",&u,&v);
edge[u].push_back(v);
In[v]++;
}
for (int i=1;i<=n;i++)
if (!In[i]) dfs1(i,-1);
memset(In,0,sizeof(In));
for (int i=1;i<n;i++){
int u,v;
scanf("%d%d",&u,&v);
pic[u].push_back(v);
In[v]++;
}
for (int i=1;i<=n;i++)
if (!In[i]) dfs2(i,-1);
printf("%lld\n",ans);
return 0;
}