DFS序+主席树 51Nod1681 公共祖先

传送门:点击打开链接

题意:有一个庞大的家族,共n人。已知这n个人的祖辈关系正好形成树形结构(即父亲向儿子连边)。

在另一个未知的平行宇宙,这n人的祖辈关系仍然是树形结构,但他们相互之间的关系却完全不同了,原来的祖先可能变成了后代,后代变成的同辈……
两个人的亲密度定义为在这两个平行宇宙有多少人一直是他们的公共祖先。

整个家族的亲密度定义为任意两个人亲密度的总和。

思路:这道题的主席树用的太巧妙了~

我们考虑点对对u节点做的贡献,如果在两棵树中u的子树的公共节点有x个,那么贡献就是x*(x-1)/2

所以我们的重点就是,如何来求公共节点个数。

我们首先对第一棵树求一遍DFS序,对每个节点编号,让树型结构变成线段结构。

但是求DFS序后,建主席树的时候,一定要搞清楚u和区间位置的关系,可以用个数组ToU来让思路更清楚

然后,再对第二棵树求一遍DFS序,对每个节点编号,那么对于一个节点u,在第二棵树对应的区间为[L,R],在第一棵树对应的区间为[l,r],那么x就等于[l,r]这些数在区间[L,R]里出现的次数,很明显我们能用主席树来维护。代码如下:

#include <map>
#include <set>
#include <cmath>
#include <ctime>
#include <stack>
#include <queue>
#include <cstdio>
#include <cctype>
#include <bitset>
#include <string>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <functional>
#define fuck(x) cout<<"["<<x<<"]";
#define FIN freopen("input.txt","r",stdin);
#define FOUT freopen("output.txt","w+",stdout);
//#pragma comment(linker, "/STACK:102400000,102400000")
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
typedef long long LL;

const int MX = 2e6 + 5;
const int MU = 1e5 + 5;
struct Edge {
    int v, nxt;
} E[MU * 2];
int Head[MU], erear;
void edge_init() {
    erear = 0;
    memset(Head, -1, sizeof(Head));
}
void edge_add(int u, int v) {
    E[erear].v = v;
    E[erear].nxt = Head[u];
    Head[u] = erear++;
}

int n, dfn, OUT[MU], o[MU], ToU[MU], root;
int AL[MU], AR[MU], BL[MU], BR[MU];
int S[MX], lson[MX], rson[MX], sz;
void DFS(int u, int f, int flag) {
    (!flag ? AL[u] : BL[u]) = ++dfn;
    for(int i = Head[u]; ~i; i = E[i].nxt) {
        int v = E[i].v;
        if(v == f) continue;
        DFS(v, u, flag);
    }
    (!flag ? AR[u] : BR[u]) = dfn;
}
void push_up(int rt) {
    S[rt] = S[lson[rt]] + S[rson[rt]];
}
void Add(int &rt, int prt, int p, int l, int r) {
    rt = ++sz; S[rt] = S[prt];
    lson[rt] = lson[prt]; rson[rt] = rson[prt];
    if(l == r) {
        S[rt]++;
        return;
    }
    int m = (l + r) >> 1;
    if(p <= m) Add(lson[rt], lson[prt], p, l, m);
    else Add(rson[rt], rson[prt], p, m + 1, r);
    push_up(rt);
}
int Query(int lrt, int rrt, int L, int R, int l, int r) {
    if(L <= l && r <= R) return S[rrt] - S[lrt];
    int m = (l + r) >> 1, ret = 0;
    if(L <= m) ret += Query(lson[lrt], lson[rrt], L, R, l, m);
    if(R > m) ret += Query(rson[lrt], rson[rrt], L, R, m + 1, r);
    return ret;
}
void Build() {
    for(int i = 1; i <= n; i++) ToU[BL[i]] = i;
    for(int i = 1; i <= n; i++) {
        Add(o[i], o[i - 1], AL[ToU[i]], 1, n);
    }
}

int main() {
    edge_init(); //FIN;
    scanf("%d", &n);
    for(int i = 1; i <= n - 1; i++) {
        int u, v;
        scanf("%d%d", &u, &v); OUT[v]++;
        edge_add(u, v); edge_add(v, u);
    }
    for(root = 1; OUT[root]; root++);
    dfn = 0; DFS(root, -1, 0);

    edge_init(); dfn = 0;
    for(int i = 1; i <= n; i++) OUT[i] = 0;
    for(int i = 1; i <= n - 1; i++) {
        int u, v;
        scanf("%d%d", &u, &v); OUT[v]++;
        edge_add(u, v); edge_add(v, u);
    }
    for(root = 1; OUT[root]; root++);
    DFS(root, -1, 1);
    Build();

    LL ans = 0;
    for(int i = 1; i <= n; i++) {
        LL c = Query(o[BL[i] - 1], o[BR[i]], AL[i], AR[i], 1, n);
        ans += (c - 1) * (c - 2) / 2;
    }
    printf("%I64d\n", ans);
    return 0;
}



实际上,主席树的思路虽然非常容易想到,但是代码量稍微大,常数稍微大。

其实这题还能不使用可持久结构,只需要使用最基础的树状数组,同样能完成该题。

总的思路和上面那种思路是一样的,我们的目的是求对每个节点的x。我们遍历第二颗树的时候,采用的是前序遍历。

首先我们在遍历子树前算出s=sum(r)-sum(l-1)的值,表示此时不包括子树时,树状数组中数字在[l,r]范围内的个数之和

然后再遍历子树,遍历完后,x=sum(r)-sum(l-1)-s,这就表示该子树的答案了!

#include <map>
#include <set>
#include <cmath>
#include <ctime>
#include <stack>
#include <queue>
#include <cstdio>
#include <cctype>
#include <bitset>
#include <string>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <functional>
#define fuck(x) cout<<"["<<x<<"]";
#define FIN freopen("input.txt","r",stdin);
#define FOUT freopen("output.txt","w+",stdout);
#pragma comment(linker, "/STACK:102400000,102400000")
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
typedef long long LL;

const int MX = 1e5 + 5;
struct Edge {
    int v, nxt;
} E[MX * 2];
int Head[MX], erear;
void edge_init() {
    erear = 0;
    memset(Head, -1, sizeof(Head));
}
void edge_add(int u, int v) {
    E[erear].v = v;
    E[erear].nxt = Head[u];
    Head[u] = erear++;
}

LL ans = 0;
int n, dfn, OUT[MX], root;
int AL[MX], AR[MX], sum[MX];
void add(int x) {
    for(; x <= n; x += x & -x) sum[x]++;
}
int ask(int x) {
    int ret = 0;
    for(; x; x -= x & -x) ret += sum[x];
    return ret;
}
void read() {
    edge_init(); dfn = 0;
    for(int i = 1; i <= n; i++) OUT[i] = 0;
    for(int i = 1; i <= n - 1; i++) {
        int u, v;
        scanf("%d%d", &u, &v); OUT[v]++;
        edge_add(u, v); edge_add(v, u);
    }
    for(root = 1; OUT[root]; root++);
}
void DFS1(int u, int f) {
    AL[u] = ++dfn;
    for(int i = Head[u]; ~i; i = E[i].nxt) {
        int v = E[i].v;
        if(v == f) continue;
        DFS1(v, u);
    }
    AR[u] = dfn;
}
void DFS2(int u, int f) {
    int sum = ask(AR[u]) - ask(AL[u] - 1);
    add(AL[u]);
    for(int i = Head[u]; ~i; i = E[i].nxt) {
        int v = E[i].v;
        if(v == f) continue;
        DFS2(v, u);
    }
    LL c = ask(AR[u]) - ask(AL[u] - 1) - sum;
    ans += (c - 1) * (c - 2) / 2;
}
int main() {
    //FIN;
    scanf("%d", &n);
    read(); DFS1(root, -1);
    read(); DFS2(root, -1);
    printf("%I64d\n", ans);
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值