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 国家有一个大工程,要给一个非常大的交通网络里建一些新的通道. 我们这个国家位置非常特殊,可以看成是一个单位边权的树,城市位于顶 ...
随机推荐
- mongoDB学习手记1--Windows系统下的安装与启动
第一步:下载安装包 我们首先需要下载 mongodb 的安装包,直接到官网下载即可.地址为:https://www.mongodb.com/download-center#community. 看下自 ...
- [js高手之路] html5 canvas动画教程 - 实时获取鼠标的当前坐标
有了前面的canvas基础之后,现在开始就精彩了,后面写的canvas教程都是属于综合应用,前面已经写了常用的canvas基础知识,参考链接如下: [js高手之路] html5 canvas系列教程 ...
- 【运维】CPU负载
最近对我的本本(4核8线程)用top命令看系统状况出现了CPU利用率超过200%的情况,非常诧异,查了下相关资料,把这个问题弄清楚了.首先来分析下CPU Load load average: 0.09 ...
- 微软的一篇ctr预估的论文:Web-Scale Bayesian Click-Through Rate Prediction for Sponsored Search Advertising in Microsoft’s Bing Search Engine。
周末看了一下这篇论文,觉得挺难的,后来想想是ICML的论文,也就明白为什么了. 先简单记录下来,以后会继续添加内容. 主要参考了论文Web-Scale Bayesian Click-Through R ...
- 计蒜客 2017 NOIP 提高组模拟赛(四)Day1 T2 小X的密室
https://nanti.jisuanke.com/t/17323 小 X 正困在一个密室里,他希望尽快逃出密室. 密室中有 N 个房间,初始时,小 X 在 1号房间,而出口在 N号房间. 密室的每 ...
- 云计算---OpenStack Neutron详解
简介: neutron是openstack核心项目之一,提供云计算环境下的虚拟网络功能 OpenStack网络(neutron)管理OpenStack环境中所有虚拟网络基础设施(VNI),物理网络基础 ...
- 浪潮之巅——IT产业的三大定律
说实话除了小说以外,从来没有什么书能让我一口气看完,更不用说IT界的书了.但是吴军老师的<浪潮之巅>这本书除外,电子版的洋洋洒洒五百多页,我一下午就将其看完了.全书通过介绍AT&T ...
- SQLserver2008r2安装过程
首先,下载SQLserver2008的安装包,下载完成打开是以下界面 点击开始安装,随着安装进程,点下一步 . 接着来到设置角色的过程,点击SQL功能安装 然后按下一步,来到功能选择,点击" ...
- Prometheus : 入门
Prometheus 是一个开源的监控系统.支持灵活的查询语言(PromQL),采用 http 协议的 pull 模式拉取数据等特点使 Prometheus 即简单易懂又功能强大. Prometheu ...
- Java面向对象 GUI 补录
Java面向对象 GUI 补录 知识概要:(1)GUI和CLI (2)AWT和SWING (3)AWT继承关系图 ...