51 nod 1681 公共祖先 (主席树+dfs序)

1681 公共祖先

基准时间限制:1 秒 空间限制:131072 KB 分值: 80 难度:5级算法题

有一个庞大的家族,共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
/*
51 nod 1681 公共祖先 (主席树+dfs序)

problem:
给你两棵树, 两个节点之间的值定义为在两个棵树中有多少一直是它们的公共祖先
求任意两个点的亲密度的总和

solve:
问题可以转换成每个点能够成为多少次公共祖先. 如果lca一直是a,b的公共祖先, 那么
a,b一定在lca的子树中.    所以找出两棵树中lca点的子树中的相同点的个数,就能计算出多少对点
在两棵树中都有lca这个公共祖先.

先处理出a树的dfs序,然后用其作为b树中dfs序的值. 在a树中,如果u在lca的子树中,那么它的序号
大于dfa[lca]小于等于eda[lca],即进出值. 所以在b树的lca的子树中找出序号在[dfa[lca],eda[lca]]
之间的个数(可以主席树维护), 就是lca子树所含相同点的个数.

hhh-2016/09/16-11:36:14
*/
#pragma comment(linker,"/STACK:124000000,124000000")
#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <vector>
#include <math.h>
#include <queue>
#include <set>
#include <map>
//#define lson  i<<1
//#define rson  i<<1|1
#define ll long long
#define clr(a,b) memset(a,b,sizeof(a))
#define scanfi(a) scanf("%d",&a)
#define scanfs(a) scanf("%s",a)
#define scanfl(a) scanf("%I64d",&a)
#define scanfd(a) scanf("%lf",&a)
#define key_val ch[ch[root][1]][0]
#define eps 1e-7
#define inf 0x3f3f3f3f3f3f3f3f
using namespace std;
const ll mod = 1e9+7;
const int maxn = 100010;
const double PI = acos(-1.0);

template<class T> void read(T&num)
{
    char CH;
    bool F=false;
    for(CH=getchar(); CH<‘0‘||CH>‘9‘; F= CH==‘-‘,CH=getchar());
    for(num=0; CH>=‘0‘&&CH<=‘9‘; num=num*10+CH-‘0‘,CH=getchar());
    F && (num=-num);
}
int stk[70], tp;
template<class T> inline void print(T p)
{
    if(!p)
    {
        puts("0");
        return;
    }
    while(p) stk[++ tp] = p%10, p/=10;
    while(tp) putchar(stk[tp--] + ‘0‘);
    putchar(‘\n‘);
}

struct Edge
{
    int to,next;
};
Edge edge[maxn*2];
int tot,head[maxn];
int in[maxn];
int cnt;
void ini()
{
    tot = 0;
    cnt = 0;
    memset(in,0,sizeof(in));
    memset(head,-1,sizeof(head));
}
void add_edge(int u,int v)
{
    edge[tot].to = v;
    edge[tot].next = head[u];
    head[u] = tot++;
}
int dfa[maxn],dfb[maxn],ta[maxn],tb[maxn];
int eda[maxn],edb[maxn];
void dfs(int u,int pre,int flag)
{
    if(!flag) dfa[u] = ++ cnt, ta[u] = cnt;
    else dfb[u] = ++ cnt, tb[cnt] = ta[u];
    for(int i = head[u]; i != -1; i = edge[i].next)
    {
        int v = edge[i].to;
        if(v == pre)continue;
        dfs(v,u,flag);
    }
    if(!flag) eda[u] = cnt;
    else edb[u] = cnt;
}

int toa;
int lson[maxn * 30],rson[maxn * 30] ;
ll c[maxn * 30];

int build(int l,int r)
{
    int root = toa ++ ;
    c[root] = 0;
    if(l != r)
    {
        int mid = (l+r) >> 1;
        build(l,mid);
        build(mid+ 1,r);
    }
    return root ;
}
int n;
int update(int root,int pos,ll val)
{
    int newroot = toa ++ ,tmp = newroot;
    c[newroot ] = c[root] + val;
    int l = 1,r = n;

    while(l < r)
    {
        int mid = (l+r) >> 1;
        if(pos <= mid)
        {
            lson[newroot] = toa ++ ,rson[newroot] = rson[root];
            newroot = lson[newroot],root = lson[root];
            r = mid;
        }
        else
        {
            rson[newroot] = toa ++ ,lson[newroot] = lson[root];
            newroot = rson[newroot] ,root = rson[root];
            l = mid + 1;
        }
        c[newroot] = c[root] + val;
    }
    return tmp;
}

ll query(int root1,int root2,int la,int ra,int l,int r)
{
    if(l >= la && r <= ra)
    {
        return c[root2] - c[root1];
    }
    int mid = (l + r) >> 1;
    ll ans = 0;
    if(la <= mid)
    {
        ans += query(lson[root1],lson[root2],la,ra,l,mid);
    }
    if(ra > mid)
    {
        ans += query(rson[root1],rson[root2],la,ra,mid+1,r);
    }
    return ans;
}

void cal(int flag)
{
    int u,v;
    ini();
    for(int i = 1; i < n; i++)
    {
        read(u),read(v);
        add_edge(u,v);
        add_edge(v,u);
        in[v] ++ ;
    }
    for(int i = 1; i <= n;i++)
    {
        if(!in[i])
        {
            dfs(i,-1,flag);
            break;
        }
    }
}
int T[maxn];
int main()
{
//    freopen("in.txt","r",stdin);
    read(n);
    toa = 0;
    cal(0);
    cal(1);
    T[0] = build(1,n);
    for(int i = 1;i <= n;i++)
    {
        T[i] = update(T[i-1],tb[i],1);
    }
    ll ans = 0;
    for(int i = 1;i <= n;i++)
    {
        int l = dfb[i],r = edb[i];
        ll t = query(T[l],T[r],dfa[i],eda[i],1,n);
        ans += 1LL*t*(t-1)/2;
    }
    printf("%I64d\n",ans);
    return 0;
}

  

时间: 2024-10-28 22:55:09

51 nod 1681 公共祖先 (主席树+dfs序)的相关文章

【BZOJ1803】Spoj1487 Query on a tree III 主席树+DFS序

[BZOJ1803]Spoj1487 Query on a tree III Description You are given a node-labeled rooted tree with n nodes. Define the query (x, k): Find the node whose label is k-th largest in the subtree of the node x. Assume no two nodes have the same labels. Input

bzoj 3772 精神污染 主席树+dfs序

精神污染 Time Limit: 10 Sec  Memory Limit: 64 MBSubmit: 637  Solved: 177[Submit][Status][Discuss] Description 兵库县位于日本列岛的中央位置,北临日本海,南面濑户内海直通太平洋,中央部位是森林和山地,与拥有关西机场的大阪府比邻而居,是关西地区面积最大的县,是集经济和文化于一体的一大地区,是日本西部门户,海陆空交通设施发达.濑户内海沿岸气候温暖,多晴天,有日本少见的贸易良港神户港所在的神户市和曾是豪

BZOJ 2809: [Apio2012]dispatching [主席树 DFS序]

传送门 题意:查询树上根节点值*子树中权值和$\le m$的最大数量 最大值是多少 求$DFS$序,然后变成区间中和$\le m$最多有几个元素,建主席树,然后权值线段树上二分就行了 $WA$:又把边表开小了..... 好吧我$zz$了有根树加无向边干什么.... #include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; #de

BZOJ_1803_Spoj1487 Query on a tree III_主席树+dfs序

BZOJ_1803_Spoj1487 Query on a tree III_主席树 Description You are given a node-labeled rooted tree with n nodes. Define the query (x, k): Find the node whose label is k-th largest in the subtree of the node x. Assume no two nodes have the same labels. I

P2633|主席树+dfs序+树链剖分求lca+离散化

不知道为什么会RE.. 待补 思路:链上求u和v两点路径第k小利用lca就转变为了 U+V-LCA-FA(LCA) 上的第k小,这因为每个点的主席树的root是从其父转移来的.可以用树链剖分求lca:在dfs序上建立主席树将树上问题转变为区间问题,询问的时候用主席树求区间k小值. 终于能写出这种题了,开心! #include<bits/stdc++.h> using namespace std; const int maxn = 1e5+100; int n,m,e = 1,num,ans=0

P3899|主席树+dfs序

理解题意后分两种情况: 1.b在a的上方: min(dep[p]-1,k)*(ll)siz[p] 因为(p点上方肯定有父亲结点b,我们不用管b是谁) 2.b在a的下方: (dep(p)+1 ~ dep(p)+k矩形框内的所有点子树个数和 所以思路:主席树维护同一深度下的各个结点子树个数和:下标是深度,权值维护的是子树个数和:在dfs序in和out时间戳上建立主席树,把树上问题转变为区间序列问题,利用dfs序时间戳的性质(子树编号在入时间戳和出时间戳的区间内),查询以p为根子树:所以问题就转变为了

Tsinsen A1505. 树(张闻涛) 倍增LCA,可持久化线段树,DFS序

题目:http://www.tsinsen.com/A1505 A1505. 树(张闻涛) 时间限制:1.0s   内存限制:512.0MB 总提交次数:196   AC次数:65   平均分:58.62 将本题分享到: 查看未格式化的试题   提交   试题讨论 试题来源 2013中国国家集训队第二次作业 问题描述 给定一棵N个节点的树,每个点有一个权值,有M个询问(a,b,c)若a 为1,回答b到c路径上的最小权值,若a为2,回答b到c路径上的最大权值,若a为3,回答b到c路径上的所有权值的

线段树+dfs序(Apple Tree )(Assign the task )

线段树+dfs序 给定一棵n个节点的树,m次查询,每次查询需要求出某个节点深度为h的所有子节点. 作为预处理,首先将树的所有节点按深度保存起来,每个深度的所有节点用一个线性结构保存,每个深度的节点相对顺序要和前序遍历一致. 然后从树的根节点进行dfs,对于每个节点记录两个信息,一个是dfs进入该节点的时间戳in[id],另一个是dfs离开该节点的时间戳out[id]. 最后对于每次查询,求节点v在深度h的所有子节点,只需将深度为h并且dfs进入时间戳在in[v]和out[v]之间的所有节点都求出

Codeforces 384E 线段树+dfs序

题目链接:点击打开链接 题意: 给定n个点,m个询问的无向树(1为根) 下面n个数表示每个点的权值 下面n-1行给出树 操作1:x点权值+v, x的第 i & 1 的儿子-v, 第 !(i&1) 的儿子+v 操作2:询问x点权值 dfs把树转成序列 根据深度把点分成2组 分别用线段树维护.. 然后Y一下 #include<stdio.h> #include<string.h> #include<iostream> #include<algorith