dfs序和欧拉序
生命不息,学习不止,昨天学了两个算法,总结一下,然而只是略懂,请路过的大佬多多谅解。
一、dfs序
1、什么是dfs序?
其实完全可以从字面意义上理解,dfs序就是指一棵树被dfs时所经过的节点的顺序
原图来源于网络,并经过灵魂画师xhk的一发魔改。
好的,这张图的dfs序显然为A-B-D-E-G-C-F-H
2、dfs序怎么写?
首先你得会写dfs(不会的请先自行学习)
然后我们都知道正常的dfs一般是长这样的(以及博主是只蒟蒻)
我们只需要多一个辅助数组来记录dfs序就行了
代码:
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std; vector<int> g[];
int dfs_[],len; void dfs(int u,int fa)
{
dfs_[++len]=u;
int sz=g[u].size();
for(int i=;i<sz;i++)
{
if(g[u][i]!=fa)
{
dfs(g[u][i],u);
}
}
} int main()
{
int n;
scanf("%d",&n);
for(int i=;i<=n;i++)
{
int from,to;
scanf("%d%d",&from,&to);
g[from].push_back(to);
g[to].push_back(from);
}
dfs(,);
for(int i=;i<=len;i++)
{
printf("%d ",dfs_[i]);
}
printf("\n");
}
好的,都应该可以理解了吧。
于是问题来了,我们要dfs序有什么用呢?
3、dfs序的用处
这得从dfs的优势来探讨了。
dfs是深度优先的,所以对于一个点,它会先遍历完它的所有子节点,再去遍历他的兄弟节点以及其他
所以对于一棵树的dfs序来说,这个点和他所有的子节点会被存储在连续的区间之中。
仍然是这张图:
原图来源于网络,并经过灵魂画师xhk的一发魔改。
我们都知道它的dfs序是:A-B-D-E-G-C-F-H
然后我们可以发现B字树B-D-E-G,C子树C-F-H都在一段连续的区间中。
那么这有什么好处呢?
比如说现在有一道题:给你一颗树,给出m个x和w,意为将x子树中的所有点加上一个权值w,最后询问所有点的权值
既然dfs序中x和他的所有子节点都在连续的区间上,那么我们就可以将它简化成差分的问题。
比如说给b节点加2,就可以简化为给b的差分数组+2,c的差分数组-2
也就是说我们只需要找到第一个不包括在B的子树的位置减掉2,到时候还原回前缀和就可以求解了。
是不是很简单?
那么问题来了,我们怎么找第一个不在B子树中的点?
这里,需要引进一个新的东西
4、时间戳
时间戳也很好理解,它就好比一个标签,贴在每一个点上,记录dfs第一次开始访问这个点的时间以及最后结束访问的时间。
所以它还是和dfs结合的
不过需要注意,dfs序和1-n是不一样的
所以可千万不要像博主一样傻傻地用s[i]来表示点i的起始时间!
那么怎么保存呢?反正博主比较愚昧,只想到用另一个数组来记录一下(这就是博主自带大常数和大空间的原因)
于是变成了这样
好的,那么知道了起始时间和终结时间以后我们该怎么用呢?
因为搜索进下一个点时时间增加,且结束时间逐级传递。
所以说我们的点的子节点的时间区间一定包含在这个点的时间区间内。
所以如果一个点的起始时间和终结时间被另一个点包括,这个点肯定是另一个点的子节点。(算导里称之为括号化定理)
因此可以判断一个点是否是另一个点的子节点。
代码:
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std; vector<int> g[];
int dfs_[],len,time,s[],e[],pos[]; void dfs(int u,int fa)
{
int x=len+;
s[++len]=++time;
dfs_[len]=u;
pos[u]=len;
int sz=g[u].size();
for(int i=;i<sz;i++)
{
if(g[u][i]!=fa)
{
dfs(g[u][i],u);
}
}
e[x]=time;
} int main()
{
int n,m;
scanf("%d %d",&n,&m);
for(int i=;i<=n;i++)
{
int from,to;
scanf("%d%d",&from,&to);
g[from].push_back(to);
g[to].push_back(from);
}
dfs(,);
for(int i=;i<=m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
x=pos[x];
y=pos[y];
if(s[x]<=s[y]&&e[y]<=e[x])
{
printf("YES\n");
}
else
{
printf("NO\n");
}
}
}
至于如何让找出第一个?
还是上面那张图,设如果是B的子节点就为1,否则0
嗯,这玩意好像可以二分呢!
于是乎就做好了!log(n)的修改,似乎还挺可做的!
好吧假装代码是这样的:
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std; vector<int> g[];
int dfs_[],len,time,s[],e[],pos[],a[],b[],sub[]; void dfs(int u,int fa)
{
int x=len+;
s[++len]=++time;
dfs_[len]=u;
b[len]=a[u];
pos[u]=len;
int sz=g[u].size();
for(int i=;i<sz;i++)
{
if(g[u][i]!=fa)
{
dfs(g[u][i],u);
}
}
e[x]=time;
} int main()
{
int n,m,t;
scanf("%d %d",&n,&m);
for(int i=;i<=n;i++)
{
scanf("%d",&a[i]);
}
for(int i=;i<=n-;i++)
{
int from,to;
scanf("%d%d",&from,&to);
g[from].push_back(to);
g[to].push_back(from);
}
dfs(,);
sub[]=b[];
for(int i=;i<=len;i++)
{
sub[i]=b[i]-b[i-];
}
for(int i=;i<=m;i++)
{
int x,w;
scanf("%d%d",&x,&w);
x=pos[x];
sub[x]+=w;
int l=x,r=a[len];
while(l<r)
{
int mid=(l+r)>>;
if(s[x]<=s[mid]&&e[mid]<=e[x])
{
l=mid+;
}
else
{
r=mid;
}
}
int y=r;
sub[y]-=w;
}
for(int i=;i<=n;i++)
{
sub[i]=sub[i-]+sub[i];
}
for(int i=;i<=n;i++)
{
int x=pos[i];
printf("%d ",sub[x]);
}
}
然后还能再优化吗?当然可以,我们只需要记录一下每个点的dfs结束的位置,这样子就不用二分了,怎么写?自己想想吧先╮( ̄▽ ̄)╭,如果你能坚持看完欧拉序,也许你能找到差不多的代码哦~
二、欧拉序
1、什么是欧拉序
就是从根结点出发,按dfs的顺序在绕回原点所经过所有点的顺序
2、欧拉序有怎么写?
(1)dfs到加进,dfs回加进,总共加入度遍。
原图来源于网络,并经过灵魂画师xhk的一发魔改。
欧拉序1为A-B-D-B-E-G-E-B-A-C-F-H-F-C-A
同时需要一个辅助数组
代码1:
#include<cmath>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std; vector<int> g[];
int len,a[]; void dfs(int u,int fa)
{
a[++len]=u;
int sz=g[u].size();
for(int i=; i<sz; i++)
{
if(g[u][i]!=fa)
{
dfs(g[u][i],u);
a[++len]=u;
}
}
} int main()
{
int t;
scanf("%d",&t);
while(t--)
{
int n,m;
len=;
memset(a,,sizeof(a));
scanf("%d",&n);
for(int i=; i<=n; i++)
{
g[i].clear();
}
for(int i=; i<=n-; i++)
{
int from,to;
scanf("%d%d",&from,&to);
g[from].push_back(to);
g[to].push_back(from);
}
dfs(,);
for(int i=;i<=len;i++)
{
printf("%d ",a[i]);
}
} }
(2)dfs进加进,dfs最后一次回加进,总共加两遍
原图来源于网络,并经过灵魂画师xhk的一发魔改。
欧拉序2为A-B-D-D-E-G-G-E-B-C-F-H-H-F-C-A
代码2:
#include<cmath>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std; vector<int> g[];
int len,a[]; void dfs(int u,int fa)
{
a[++len]=u;
int sz=g[u].size();
for(int i=; i<sz; i++)
{
if(g[u][i]!=fa) dfs(g[u][i],u);
}
}
a[++len]=u;
} int main()
{
int t;
scanf("%d",&t);
while(t--)
{
int n,m;
len=;
memset(a,,sizeof(a));
scanf("%d",&n);
for(int i=; i<=n; i++)
{
g[i].clear();
}
for(int i=; i<=n-; i++)
{
int from,to;
scanf("%d%d",&from,&to);
g[from].push_back(to);
g[to].push_back(from);
}
dfs(,);
for(int i=;i<=len;i++)
{
printf("%d ",a[i]);
}
} }
当然还有几种写法,各有长处,不在介绍了就。
好的,那么我们来讲下这几种欧拉序的用处
三、欧拉序的用处
1、求LCA
假设我们使用欧拉序1
则我们要求的两个点在欧拉序中的第一个位置之间肯定包含他们的lca,因为欧拉序1上任意两点之间肯定包含从第一个点走到第二个点访问的路径上的所有点
所以只需要记录他们的深度,然后从两个询问子节点x,y第一次出现的位置之间的深度最小值即可,可能不大好理解,看张图吧。
代码:
#include<cmath>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std; vector<int > g[];
int len,a[],dep[],pos[][],dp[][],vis[],cnt[]; void dfs(int u,int fa,int deep)
{
a[++len]=u;
dep[len]=deep+;
if(!vis[u])
{
cnt[u]=len;
vis[u]=;
}
int sz=g[u].size();
for(int i=; i<sz; i++)
{
if(g[u][i]!=fa)
{
dfs(g[u][i],u,deep+);
a[++len]=u;
dep[len]=deep+;
}
}
} int main()
{
int t;
scanf("%d",&t);
while(t--)
{
int n,m;
len=;
memset(a,,sizeof(a));
memset(dep,,sizeof(dep));
memset(pos,,sizeof(pos));
memset(dp,,sizeof(dp));
memset(vis,,sizeof(vis));
memset(cnt,,sizeof(cnt));
scanf("%d%d",&n,&m);
for(int i=; i<=n; i++)
{
g[i].clear();
}
for(int i=; i<=n-; i++)
{
int from,to;
scanf("%d%d",&from,&to);
g[from].push_back(to);
g[to].push_back(from);
}
dfs(,,);
printf("%d\n",len);
for(int i=; i<=len; i++)
{
dp[i][]=dep[i];
pos[i][]=i;
}
for(int j=; j<=; j++)
{
for(int i=; i<=len; i++)
{
if(i+(<<(j-))>=len)
{
break;
}
if(dp[i][j-]>dp[i+(<<(j-))][j-])
{
dp[i][j]=dp[i+(<<(j-))][j-];
pos[i][j]=pos[i+(<<(j-))][j-];
}
else
{
dp[i][j]=dp[i][j-];
pos[i][j]=pos[i][j-];
}
}
}
for(int i=; i<=m; i++)
{
int x,y;
scanf("%d%d",&x,&y);
int dx=cnt[x];
int dy=cnt[y];
if(dx>dy)
{
swap(dx,dy);
swap(x,y);
}
int k=(int)(log((double)(dy-dx+))/log(2.0));
int p;
if(dp[dx][k]>dp[dy-(<<k)+][k])
{
p=pos[dy-(<<k)+][k];
}
else
{
p=pos[dx][k];
}
printf("%d\n",a[p]);
}
} }
例题:HDU 2586
2、求子树的权值之和
先来道滑稽题:(由于纯属手糊,如有错误,还请谅解)
给你一棵树,告诉你每个点的点权,给你m个x和w,表示将子树x中每个点的权值和加w,然后再给你t个x,表示询问x子树中所有点的权值之和.
样例输入:
7 2 7
6 5 4 2 1 8 7
1 2
2 3
2 4
4 5
1 6
6 7
5 1
3 2
1
2
3
4
5
6
7
样例输出:
36 15 6 4 2 15 7
这道题和dfs序中那道很像不是吗?
好,我们来看欧拉序2,它有什么优点呢?你可以发现,每个点都出现了两遍,而这个点第一次出现和第二次出现的位置之间就是他的所有子孙节点.
ちょっと まで!
有没有发现这就是之前记录结束位置的想法?
只不过这个更具体更好懂呢!
然后同样是差分,只不过差分时我们可以直接通过该点第二次出现的位置+1来获得第一个不是该子树的点,然后,o(1)的修改就实现了.
但是如果这样怎么查询权值和呢?
我们都知道这个点第一次出现的位置和第二次出现的位置之间是他的子树,那么我们只需要维护一遍前缀和,用第二个的位置减第一个的位置前的那个位置,就可以得到权值和了.
不过由于每个子树中的点都被算了两遍,我们要除以二.
代码:
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std; vector<int> g[];
int euler[],pos[][],len,a[],sub[]; void dfs(int u,int fa)
{
euler[++len]=u;
pos[u][]=len;
int sz=g[u].size();
for(int i=;i<sz;i++)
{
if(g[u][i]!=fa)
{
dfs(g[u][i],u);
}
}
euler[++len]=u;
pos[u][]=len;
} int main()
{
int n,m,t;
len=;
scanf("%d%d%d",&n,&m,&t);
for(int i=;i<=n;i++)
{
scanf("%d",&a[i]);
}
for(int i=;i<=n-;i++)
{
int from,to;
scanf("%d%d",&from,&to);
g[from].push_back(to);
g[to].push_back(from);
}
dfs(,);
sub[]=a[euler[]];
for(int i=;i<=len;i++)
{
sub[i]=a[euler[i]]-a[euler[i-]];
}
for(int i=;i<=m;i++)
{
int x,w;
scanf("%d %d",&x,&w);
sub[pos[x][]]+=w;
sub[pos[x][]+]-=w;
}
for(int i=;i<=len;i++)
{
sub[i]=sub[i-]+sub[i];
}
for(int i=;i<=len;i++) //如果删去维护前缀和的过程,就是查询单个点的权值
{
sub[i]=sub[i-]+sub[i];
}
for(int i=;i<=t;i++)
{
int x;
scanf("%d",&x);
printf("%d\n",(sub[pos[x][]]-sub[pos[x][]-])/);
}
}
沉迷学习,无法自拔......
dfs序和欧拉序的更多相关文章
- 图论——Tarjan 初步 DFS序+时间戳+欧拉序
一.什么是DFS序: DFS序是按照先序遍历,先遍历根节点然后依次遍历左子树,右子树的过程,每次遇到新的节点就把新访问节点加到序列中,代码如下: int DFSrk[100000]; int cnt= ...
- LCA-RMQ+欧拉序
还是那一道洛谷的板子题来说吧 传送门 其实好几天之前就写了 结果dr实在是太弱了 没有那么多的精力 于是就一直咕咕咕了 哎 今天终于补上来了 LCA概念传送门 RMQ传送门 这个算法是基于RMQ和欧拉 ...
- Underground Lab CodeForces - 782E (欧拉序)
大意:$n$结点,$m$条边无向图, 有$k$个人, 每个人最多走$\left\lceil\frac {2n}{k}\right\rceil$步, 求一种方案使得$k$个人走遍所有的点 $n$结点树的 ...
- lca 欧拉序+rmq(st) 欧拉序+rmq(线段树) 离线dfs 倍增
https://www.luogu.org/problemnew/show/P3379 1.欧拉序+rmq(st) /* 在这里,对于一个数,选择最左边的 选择任意一个都可以,[left_index, ...
- 树的遍历顺序 - dfs序|欧拉序|dfn序(备忘)
(仅作备忘) dfs序是dfs过程中对于某节点进入这个节点的子树和离开子树的顺序 满足每个节点都会在dfs序上出现恰好两次 任意子树的dfs序都是连续的 欧拉序是dfs过程中经过节点的顺序 每个节点至 ...
- dfs 序 欧拉序
推荐博客 :https://www.cnblogs.com/stxy-ferryman/p/7741970.html DFS序其实就是一棵树顺次访问的结点的顺序,例如下面这棵树 它的 dfs 序就是 ...
- Bzoj 2286 & Luogu P2495 消耗战(LCA+虚树+欧拉序)
题面 洛谷 Bzoj 题解 很容易想到$O(nk)$的树形$dp$吧,设$f[i]$表示处理完这$i$颗子树的最小花费,同时再设一个$mi[i]$表示$i$到根节点$1$路径上的距离最小值.于是有: ...
- 【BZOJ 3772】精神污染 主席树+欧拉序
这道题的内存…………………真·精神污染……….. 这道题的思路很明了,我们就是要找每一个路径包含了多少其他路径那么就是找,有多少路径的左右端点都在这条路径上,对于每一条路径,我们随便选定一个端点作为第 ...
- 【BZOJ3611】[Heoi2014]大工程 欧拉序+ST表+单调栈
[BZOJ3611][Heoi2014]大工程 Description 国家有一个大工程,要给一个非常大的交通网络里建一些新的通道. 我们这个国家位置非常特殊,可以看成是一个单位边权的树,城市位于顶 ...
随机推荐
- js如何获取客户端IP
1.在HTML页面里面引入<script src="http://pv.sohu.com/cityjson?ie=utf-8"></script> 2.获取 ...
- java GUI编程二
java基础学习总结--GUI编程(二) 一.事件监听 测试代码一: 1 package cn.javastudy.summary; 2 3 import java.awt.*; 4 import j ...
- C++11获取线程的返回值
C++11 std::future and std::promise 在许多时候,我们会有这样的需求--即我们想要得到线程返回的值. 但是在C++11 多线程中我们注意到,std::thread对象会 ...
- JAVA多线程---volatile关键字
加锁机制既可以确保可见性又可以保证原子性,而volatile变量只能确保可见性. 当把变量声明为volatile时候 编译器与运行时都会注意到这个变量是共享的,不会将该变量上的操作与其他内存操作一起重 ...
- JSP入门 导出文件
1.图片校验码 <img src="captcha.jpg" /> web.xml配置 <servlet> <servlet-name& ...
- Spring Boot-------JPA——EntityManager构建通用DAO
EntityManager EntityManager 是用来对实体Bean 进行操作的辅助类.他可以用来产生/删除持久化的实体Bean,通过主键查找实体bean,也可以通过EJB3 QL 语言查找满 ...
- golang 标准库间依赖的可视化展示
简介 国庆看完 << Go 语言圣经 >>,总想做点什么,来加深下印象.以可视化的方式展示 golang 标准库之间的依赖,可能是一个比较好的切入点.做之前,简单搜了下相关的内 ...
- 计蒜客 2017 NOIP 提高组模拟赛(四)Day1 T2 小X的密室
https://nanti.jisuanke.com/t/17323 小 X 正困在一个密室里,他希望尽快逃出密室. 密室中有 N 个房间,初始时,小 X 在 1号房间,而出口在 N号房间. 密室的每 ...
- c# datetime与 timeStamp(unix时间戳) 互相转换
/// <summary> /// Unix时间戳转为C#格式时间 /// </summary> /// <param name="timeStamp" ...
- win10 UWP 申请微软开发者
申请微软开发者可以到https://dev.windows.com/zh-cn/programs/join 如果是学生,先去http://www.dreamspark.com/ 如果是英文,点stud ...