题目描述
猴猴今天要和小伙伴猩猩比赛爬树,为了公平不碰撞,猴猴和猩猩需要在不同的树上攀爬。于是它们选了两颗节点数同为n的树,并将两棵树的节点分别以1~n标号(根节点标号为1),但两棵树的节点连接方式不尽相同。
现在它们决定选择两个标号的点进行比赛。为了方便统计,规定它们比赛中必须都向上爬。(即选定的赛段节点u→节点v都必须指向叶子方向)请你求出这两棵树上共有多少对节点满足比赛的需求。
题目解析
先求出第一棵树的 d f s dfs dfs序,得到每个节点本身的编号以及子树的编号范围 [ l i , r i ] [l_i,r_i] [li,ri] 。 用树状数组来维护后面的计算。 在 D F S DFS DFS处理第二棵树时,我们每处理完一个节点,就将该节点在第一棵树中的对应编号设为 1 1 1,处理完所有子节点后,查询编号区间 [ l i , r i ] [l_i,r_i] [li,ri]的区间和,即当前子树下,有多少个节点在树 1 1 1中也是当前节点的子孙节点。维护计数即可。
代码
#include<cstdio>
#include<cstring>
using namespace std;
const int N=100000+5;
int n,cnt,ls[N],next[2*N],v[2*N];
inline void add(int x,int y)
{
next[++cnt]=ls[x]; ls[x]=cnt;v[cnt]=y;
next[++cnt]=ls[y]; ls[y]=cnt;v[cnt]=x;
}
int id,dfn[N],siz[N];
void pre(int x,int fa)
{
dfn[x]=++id; siz[x]=1;
for(int i=ls[x];i;i=next[i])
{
int y=v[i];
if(y==fa) continue;
pre(y,x);
siz[x]+=siz[y];
}
}
int t[N],ans[N];
void change(int x,int num) {for(;x<=n;x+=x&-x) t[x]+=num;}
int ask(int x) {int ret=0;for(;x;x-=x&-x) ret+=t[x];return ret;}
void dfs(int x,int fa)
{
int cmp=0;
for(int i=ls[x];i;i=next[i])
{
int y=v[i];
if(y==fa) continue;
cmp=ask(dfn[y]+siz[y]-1)-ask(dfn[y]-1);
dfs(y,x);
ans[y]-=cmp;
change(dfn[y],1);
}
ans[x]=ask(dfn[x]+siz[x]-1)-ask(dfn[x]-1);
}
int main()
{
int x,y; scanf("%d",&n);
for(int i=1;i<n;i++) scanf("%d%d",&x,&y),add(x,y);
pre(1,0);
cnt=0;memset(ls,0,sizeof(ls));
for(int i=1;i<n;i++) scanf("%d%d",&x,&y),add(x,y);
dfs(1,0);
for(int i=1;i<=n;i++) ans[0]+=ans[i];
printf("%d",ans[0]);
return 0;
}