[51nod 1681]公共祖先(dfs序+线段树合并)
[51nod 1681]公共祖先(dfs序+线段树合并)
题面
给出两棵n(n<=100000)个点的树,对于所有点对求它们在两棵树中公共的公共祖先数量之和。

如图,对于点对(2,4),它们在第一棵树里的公共祖先为{1,3,5},在第二棵树里的公共祖先为{1},因此公共的公共祖先数量为2
把所有点对的这个数量加起来,就得到了最终答案
分析
\(O(n^3)\)的暴力不讲了,先考虑\(O(n^2)\)的做法
枚举点对复杂度太高,不可行。我们考虑每个节点x作为公共的公共祖先的次数。设树A上的节点x,在树B上对应的节点是x'(实际上x'和x的编号是相同的,只是这样方便描述).则如果点对既在x的子树中,对应到B上后又在x'的子树中,则这个点对的公共的公共祖先就包含x .注意一个小细节,如果x是y的父亲,x不算做x和y的祖先,所以这里的“子树”应该不包含x.

如这张图中,A中1的子树中节点有{2,3,4,5},{2,3,4,5}对应到B中均在1的子树内。这4个节点中任选一对,它们的公共祖先都包含1
那么我们只要考虑x的子树中有多少个点对应过去在树B上x'的子树中即可。暴力枚举x子树中的每个节点,然后判断。设这样的点个数为cnt,则x作为公共的公共祖先的次数就是\(C_{cnt}^2\),把它累加进答案
那么我们怎么把它优化呢?我们发现,节点编号是离散的,不好判断。但子树中节点的dfs序是连续的。我们把A中节点x的dfs序标记到树B上对应的位置x‘。然后我们遍历树A的每个节点x,它子树的dfs序范围为[l[x]+1,r[x]] (不包含x)。那么问题就变成在树B上编号为x的节点的子树中有多少个节点的标记落在[l[x]+1,r[x]]的范围内。

如图,我们想求A中3的子树中有多少个节点对应到B中也在3的子树里,l[3]=2,r[3]=5,B中3的子树中的dfs序有{2,4},落在[2+1,5]的范围内的只有4,所以有1个节点
这是线段树合并的经典问题。用权值线段树合并就可以了,节点x的线段树的节点[l,r] 存储有x的子树中多少个值落在[l,r]内。(有些题解用了可持久化线段树,其实没有必要)。我们遍历的时候从下往上合并,合并到节点x的时候就更新x的cnt值。
时间复杂度\(O(n\log n)\)
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#define maxn 100000
#define maxlogn 25
using namespace std;
int n;
struct segment_tree{
#define lson(x) (tree[x].ls)
#define rson(x) (tree[x].rs)
struct node{
int ls;
int rs;
int val;
}tree[maxn*maxlogn+5];
int ptr;
void push_up(int x){
tree[x].val=tree[lson(x)].val+tree[rson(x)].val;
}
void update(int &x,int upos,int l,int r){
if(!x) x=++ptr;
if(l==r){
tree[x].val++;
return;
}
int mid=(l+r)>>1;
if(upos<=mid) update(tree[x].ls,upos,l,mid);
else update(tree[x].rs,upos,mid+1,r);
push_up(x);
}
int query(int x,int L,int R,int l,int r){
if(L<=l&&R>=r){
return tree[x].val;
}
int mid=(l+r)>>1;
int ans=0;
if(L<=mid) ans+=query(tree[x].ls,L,R,l,mid);
if(R>mid) ans+=query(tree[x].rs,L,R,mid+1,r);
return ans;
}
int merge(int x,int y,int l,int r){
if(!x||!y) return x+y;
if(l==r){
tree[x].val+=tree[y].val;
return x;
}
int mid=(l+r)>>1;
tree[x].ls=merge(tree[x].ls,tree[y].ls,l,mid);
tree[x].rs=merge(tree[x].rs,tree[y].rs,mid+1,r);
push_up(x);
return x;
}
}T;
int root[maxn+5];
int in[maxn+5];
int tim=0;
int dfnl[maxn+5],dfnr[maxn+5];
vector<int>E1[maxn+5],E2[maxn+5];
void dfs1(int x,int fa){
dfnl[x]=++tim;
for(int i=0;i<E1[x].size();i++){
int y=E1[x][i];
if(y!=fa){
dfs1(y,x);
}
}
dfnr[x]=tim;
}
int cnt[maxn+5];
void dfs2(int x,int fa){
for(int i=0;i<E2[x].size();i++){
int y=E2[x][i];
if(y!=fa){
dfs2(y,x);
root[x]=T.merge(root[x],root[y],1,n);
}
}
cnt[x]=T.query(root[x],dfnl[x]+1,dfnr[x],1,n);
}
int main(){
int u,v;
int rt1,rt2;
scanf("%d",&n);
for(int i=1;i<n;i++){
scanf("%d %d",&u,&v);
E1[u].push_back(v);
E1[v].push_back(u);
in[v]++;
}
for(int i=1;i<=n;i++) if(in[i]==0) rt1=i;//根不一定是1
memset(in,0,sizeof(in));
for(int i=1;i<n;i++){
scanf("%d %d",&u,&v);
E2[u].push_back(v);
E2[v].push_back(u);
in[v]++;
}
for(int i=1;i<=n;i++) if(in[i]==0) rt2=i;
dfs1(rt1,0);
for(int i=1;i<=n;i++){
T.update(root[i],dfnl[i],1,n);
}
dfs2(rt2,0);
long long ans=0;
for(int i=1;i<=n;i++){
ans+=(long long)cnt[i]*(cnt[i]-1)/2;
}
printf("%lld\n",ans);
}
[51nod 1681]公共祖先(dfs序+线段树合并)的更多相关文章
- 51nod 1681 公共祖先 | 树状数组
51nod 1681 公共祖先 有一个庞大的家族,共n人.已知这n个人的祖辈关系正好形成树形结构(即父亲向儿子连边). 在另一个未知的平行宇宙,这n人的祖辈关系仍然是树形结构,但他们相互之间的关系却完 ...
- Codeforces 343D Water Tree(DFS序 + 线段树)
题目大概说给一棵树,进行以下3个操作:把某结点为根的子树中各个结点值设为1.把某结点以及其各个祖先值设为0.询问某结点的值. 对于第一个操作就是经典的DFS序+线段树了.而对于第二个操作,考虑再维护一 ...
- 【XSY2667】摧毁图状树 贪心 堆 DFS序 线段树
题目大意 给你一棵有根树,有\(n\)个点.还有一个参数\(k\).你每次要删除一条长度为\(k\)(\(k\)个点)的祖先-后代链,问你最少几次删完.现在有\(q\)个询问,每次给你一个\(k\), ...
- BZOJ4551[Tjoi2016&Heoi2016]树——dfs序+线段树/树链剖分+线段树
题目描述 在2016年,佳媛姐姐刚刚学习了树,非常开心.现在他想解决这样一个问题:给定一颗有根树(根为1),有以下 两种操作:1. 标记操作:对某个结点打上标记(在最开始,只有结点1有标记,其他结点均 ...
- 【洛谷2982】[Usaco2010 Feb]慢下来Slowdown(dfs序+线段树)
题目: 洛谷2982 分析: 这道题最重要的是想明白一点:牛\(i\)走到以后只对\(P_i\)的子树产生影响 知道这个以后,就可以想到在线维护每个牧场已经被"影响"了多少次(也就 ...
- 【cf343】D. Water Tree(dfs序+线段树)
传送门 题意: 给出一个以\(1\)为根的有根树,起始每个结点都为\(0\),现在有三种操作: 1.将\(v\)及\(v\)的子树都置为\(1\): 2.将\(v\)及其所有的祖先都置为\(0\): ...
- Educational Codeforces Round 6 E dfs序+线段树
题意:给出一颗有根树的构造和一开始每个点的颜色 有两种操作 1 : 给定点的子树群体涂色 2 : 求给定点的子树中有多少种颜色 比较容易想到dfs序+线段树去做 dfs序是很久以前看的bilibili ...
- 【BZOJ-3252】攻略 DFS序 + 线段树 + 贪心
3252: 攻略 Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 339 Solved: 130[Submit][Status][Discuss] D ...
- BZOJ2434 [Noi2011]阿狸的打字机(AC自动机 + fail树 + DFS序 + 线段树)
题目这么说的: 阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机.打字机上只有28个按键,分别印有26个小写英文字母和'B'.'P'两个字母.经阿狸研究发现,这个打字机是这样工作的: 输入小 ...
随机推荐
- linux tcp/ip 调优
sysctl 变量修改方法:sysctl –a 使用 sysctl 命令修改系统变量,和通过编辑 sysctl.conf 文件来修改系统变量两种.但并不是所有的 变量都可以在这个模式下设定. 注:sy ...
- vue的请求数据方式
一,vue-resource请求数据 介绍:vue-resource请求数据方式是官方提供的一个插件 步骤: 1,npm安装 npm install vue-resource --save ...
- 【改】utf-8 的去掉BOM的方法
最近在测试中发现,linux系统中导出的文件,有记事本打开另存为或者保存后,再次导入进linux系统,发现失败了,对比文件内容,没发现区别,打开二进制文件对比发现,文件头部多了三个字符:EF BB B ...
- [python 学习] python 多线程
1. # -*- coding: utf-8 -*- import threading import time import random def go(name): for i in range(2 ...
- [web 安全] 源码泄露
web 源码泄露 1..hg 源码泄露 http://www.example.com/.hg/ 2..git 源码泄露 http://www.example.com/.git/config 3..ds ...
- POJ 2018 Best Cow Fences (二分答案构造新权值 or 斜率优化)
$ POJ~2018~Best~Cow~ Fences $(二分答案构造新权值) $ solution: $ 题目大意: 给定正整数数列 $ A $ ,求一个平均数最大的长度不小于 $ L $ 的子段 ...
- 22.Express框架——2019年12月19日
2019年12月19日14:16:36 1. express简介 1.1 介绍 Express框架是后台的Node框架,所以和jQuery.zepto.yui.bootstrap都不一个东西. Exp ...
- Redirecting to /bin/systemctl restart mysql. service Failed to restart mysql.service: Unit not found.
使用如下命令操作mysql即可: systemctl restart mysqld.service systemctl start mysqld.service systemctl stop mysq ...
- CSS中浮动属性float及清除浮动
1.float属性 CSS 的 Float(浮动),会使元素向左或向右移动,由于浮动的元素会脱离文档流,所以它后面的元素会重新排列. 浮动元素之后的那些元素将会围绕它,而浮动元素之前的元素将不会受到影 ...
- GEI步态能量图生成
步态能量图生成主要有两步,主要为: 在原始轮廓图上对人的轮廓进行裁剪,在下面制作步态能量图图片叠加以什么为中心位置也是一个问题.一般有两种方式,一种是中心位置为人体宽的一半.另一种是以头顶为中心位置. ...