51nod1681公共祖先(dfs序+树状数组)

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

solution:

首先想到的是最最暴力的做法,对两棵树的每一个点分别记录它的所有儿子,然后枚举每一对点统计答案。这是O(n³)……然后很容易想到在dfs时记录下上述信息,这样就是O(n²)的。把所有信息记下来显然太暴力了,所以需要一边做一边统计答案。枚举公共祖先,假设有sum个点在两棵树中都是它的后代,那么以这个点为公共祖先的贡献是sum*(sum-1)/2。所以只要快速算出sum就行了。对于第一棵树做dfs,记录下每一个点的入栈时间戳和出栈时间戳,那么两个时间戳中间的点是且都是它的后代。(这一点和树链剖分很像)。然后对第二棵树进行dfs,并根据第一棵树的dfs序维护树状数组。访问到u点时,统计sum1为u的后代的点权和,然后将u点的权+1,访问u的所有儿子,在出栈时记录sum2为此时u的后代的点权和。sum=sum2-sum1。时间复杂度O(n lg n),问题就解决了。

code:

#include<stdio.h>
long long ans;
int de1[100005],num,de2[100005],n,en1,Next1[100005],vet1[100005],head1[100005],en2,Next2[100005],vet2[100005],head2[100005],bit[100005],be[100005],en[100005],root1,root2;
inline void addedge1(int u,int v){de1[v]++;Next1[++en1]=head1[u];vet1[en1]=v;head1[u]=en1;}
inline void addedge2(int u,int v){de2[v]++;Next2[++en2]=head2[u];vet2[en2]=v;head2[u]=en2;}
inline int lowbit(int x){return x&-x;}
inline void add(int x,int y){while(x<=n){bit[x]+=y;x+=lowbit(x);}}
inline int query(int x){int ans=0;while(x){ans+=bit[x];x-=lowbit(x);}return ans;}
void dfs1(int u){
    be[u]=en[u]=++num;
    for(int i=head1[u];i;i=Next1[i])dfs1(vet1[i]);
    en[u]=num;
}
void dfs2(int u){
    long long sum=query(en[u])-query(be[u]-1);
    add(be[u],1);
    for(int i=head2[u];i;i=Next2[i])
        dfs2(vet2[i]);
    sum=query(en[u])-query(be[u]-1)-sum-1;
    ans+=(sum-1ll)*sum>>1;
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<n;i++){int x,y;scanf("%d%d",&x,&y);addedge1(x,y);}
    for(int i=1;i<n;i++){int x,y;scanf("%d%d",&x,&y);addedge2(x,y);}
    for(int i=1;i<n;i++){if(!de1[i])root1=i;if(!de2[i])root2=i;}
    dfs1(root1);dfs2(root2);
    printf("%lld\n",ans);
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值