51Nod 1681 公共祖先(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序,就是看这两个dfs中有多少个是相同的。

解法一:遍历第一个的时候,记录每个节点的子树的dfs序的范围,遍历第二棵树的时候分别记录遍历一棵子树前后 l[i]~r[i] 范围内有多少点被遍历过(树状数组维护),设二者之差为x,ans += x * (x - 1) / 2 即可

#include <bits/stdc++.h>
#define IO_read ios::sync_with_stdio(false);cin.tie(0)
#define fre freopen("in.txt", "r", stdin)
#define _for(i,a,b) for(int i=a; i< b; i++)
#define _rep(i,a,b) for(int i=a; i<=b; i++)
#define lowbit(a) ((a)&-(a))
using namespace std;
typedef long long ll;
template <class T>
void read(T &x)
{
    char c; bool op=0;
    while(c=getchar(), c<'0'||c>'9') if(c=='-') op=1;
    x=c-'0';
    while(c=getchar(), c>='0'&& c<='9') x=x*10+c-'0';
    if(op) x=-x;
}
template <class T>
void write(T x)
{
    if(x<0) putchar('-'), x=-x;
    if(x>=10) write(x/10);
    putchar('0'+x % 10);
}

const int maxn=1e5+5;

int n;

struct Edge{
    int to, next;
}edge[maxn];
int head[maxn], is_rt[maxn], tot;
void addedge(int u, int v)
{
    is_rt[v]=true; edge[++tot].to=v; edge[tot].next=head[u]; head[u]=tot;
}

int l[maxn], r[maxn], dfsn[maxn];
int cnt_dfs;
void dfs1(int u)
{
    dfsn[u]=l[u]=++cnt_dfs;
    for(int i=head[u]; i!=-1; i=edge[i].next)
    {
        int v=edge[i].to;
        dfs1(v);
    }
    r[u]=cnt_dfs; //注意这里不是++cnt_dfs
}

ll tree[maxn];
void add(int x, int val)
{
    for(int i=x; i<=n; i+=lowbit(i))
        tree[i]+=val;
}

ll ask(int x)
{
    ll res=0;
    for(int i=x; i; i-=lowbit(i))
        res+=tree[i];
    return res;
}

ll ans=0;
void dfs2(int u)
{
    int pre=ask(r[u])-ask(l[u]-1);
    for(int i=head[u]; i!=-1; i=edge[i].next)
    {
        int v=edge[i].to;
        add(dfsn[v], 1);
        dfs2(v);
    }
    int now=ask(r[u])-ask(l[u]-1);
    int k=now-pre;
    ans+=1ll*k*(k-1)/2;
}

void init()
{
    memset(head, -1, sizeof(head));
    memset(is_rt, 0, sizeof(is_rt));
    tot=cnt_dfs=0;
}

int main()
{
    //fre;
    memset(head, -1, sizeof(head));
    read(n);
    for(int i=1, u, v; i<=n-1; i++)
        read(u), read(v), addedge(u,v);
    _rep(i, 1, n)
        if(!is_rt[i]) dfs1(i);
    init();
    for(int i=1, u, v; i<=n-1; i++)
        read(u), read(v), addedge(u,v);
    _rep(i, 1, n)
        if(!is_rt[i]) dfs2(i);
    write(ans), putchar('\n');
    return 0;
}
View Code

 

解法二:查询两个序列的两个区间有所少数是相同的 主席树搞一下就行 这有个这类型的裸题 先把这个做明白再看这道题 http://codeforces.com/problemset/problem/323/C

posted @ 2019-10-23 13:10  N_Yokel  阅读(172)  评论(0)    收藏  举报