51nod:公共祖先(主席树 & DFS序)

有一个庞大的家族,共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序将子树变成连续的编号,就可以通过“树状数组”或者“主席树”来统计子树有几个相同的点了。这里练习一下主席树。

# include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5+30;
struct node{int l, r, sum;}T[maxn*30];//开小了会T
vector<int>g[maxn];
int root[maxn], in[maxn],out[maxn], in2[maxn], tot=0, n, cnt=0;
int du[maxn];
long long ans = 0;
inline void scan(int &ret)
{
    char c; ret = 0;
    while ((c = getchar())<'0'||c>'9');
    while(c>='0'&&c<='9') ret = ret*10+(c-'0'), c = getchar();
}
inline void Out(long long x)
{
    if(x/10) Out(x/10);
    putchar('0'+x%10);
}
void update(int l, int r, int &x, int y, int val)
{
    T[++cnt] = T[y], ++T[cnt].sum, x=cnt;
    if(l==r) return;
    int mid=l+r>>1;
    if(val<=mid) update(l, mid, T[x].l, T[y].l, val);
    else update(mid+1, r, T[x].r, T[y].r, val);
}
int query(int l, int r, int ll, int rr, int x, int y)
{
    if(ll<=l && rr>=r) return T[x].sum-T[y].sum;
    int mid=l+r>>1, tmp=0;
    if(ll<=mid) tmp += query(l, mid, ll, rr, T[x].l, T[y].l);
    if(rr>mid) tmp += query(mid+1, r, ll, rr, T[x].r, T[y].r);
    return tmp;
}
void dfs(int cur)
{
    in[cur]=++tot;
    for(int to:g[cur]) dfs(to);
    out[cur]=tot;
}
void dfs2(int cur, int fa)
{
    in2[cur]=++tot;
    update(1, n, root[tot], root[fa], in[cur]);
    for(int to:g[cur]) dfs2(to, tot);
    if(in[cur]==out[cur] || tot==in2[cur]) return;
    int tmp = query(1, n, in[cur]+1, out[cur], root[tot], root[in2[cur]]);
    ans += (long long)tmp*(tmp-1)/2;
}
int main()
{
    int x, y, base;
    scan(n);
    for(int i=1; i<n; ++i)
    {
        scan(x), scan(y);
        g[x].push_back(y);
        ++du[y];
    }
    for(int i=1; i<=n; ++i) if(du[i]==0){base=i; break;}
    dfs(base); tot = 0;
    for(int i=1; i<=n; ++i) g[i].clear(), du[i]=0;
    for(int i=1; i<n; ++i)
    {
        scan(x), scan(y);
        g[x].push_back(y);
        ++du[y];
    }
    for(int i=1; i<=n; ++i) if(du[i]==0){base=i; break;}
    dfs2(base, 0);
    Out(ans);
    putchar('\n');
    return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值