dfs、bfs序
dfs序
所以对于一棵树的\(dfs\)序来说,这个点和他所有的子节点会被存储在连续的区间之中。
所以如果一个点的起始时间和终结时间被另一个点包括,这个点肯定是另一个点的子节点。(括号化定理)
欧拉序有两种形式:
1 在每个结点进和出时加进序列。
2 只要到达每一个结点就把他加进序列。 —— 用于求LCA
以下代码为1
int dfn_cnt=0;
inline void dfs(int x,int f) {
in[x]=++dfn_cnt;rev[dfn_cnt]=x;
for(int i=hd[x];i;i=nxt[i]) {
int y=to[i];
if(y==f) continue;
dfs(y,x);
}
out[x]=dfn_cnt;
}
//dep树的深度
void dfs(int x,int pre,int d){
in[x]=++tot;
dep[x]=d;
for(int i=0;i<e[x].size();i++){
int y=e[x][i];
if(y==pre)continue;
dfs(y,x,d+1);
}
out[x]=tot;
}
应用1--1
点修改,子树和查询
由于X的子树在DFS序中是连续的一段, 只需要维护一个dfs序列
用树状数组实现 : 单点修改和区间查询.
#include<cstdio>
using namespace std;
const int N=200005;
int in[N],out[N];
int n,m,dfn_cnt;
bool a[N];
int hd[N],nxt[N],to[N],tot;
void add(int x,int y) {
to[++tot]=y;nxt[tot]=hd[x];hd[x]=tot;
}
void dfs(int x,int f){
in[x]=++dfn_cnt;
for(int i=hd[x];i;i=nxt[i]){
if(to[i]==f) continue;
dfs(to[i],x);
}
out[x]=dfn_cnt;
}
int c[N];
void update(int x,int num){
while(x<=n){
c[x]+=num;
x+=x&(-x);
}
}
int query(int x){
int ans=0;
while(x>0){
ans+=c[x];
x-=x&(-x);
}
return ans;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;++i){
a[i]=1;
update(i,1);
}
for(int i=1;i<n;++i){
int u,v;
scanf("%d%d",&u,&v);
add(u,v);
add(v,u);
}
dfs(1,0);
scanf("%d",&m);
char op;
int t;
while(m--){
scanf(" %c%d",&op,&t);
if(op=='C'){
if(a[t]){
a[t]=0;
update(in[t],-1);
}
else{
a[t]=1;
update(in[t],1);
}
}
else
printf("%d\n",query(out[t])-query(in[t]-1));
}
return 0;
}
应用1--2
树链修改,单点查询
a. 对X到根节点路径上所有点权加W
b. 对Y到根节点路径上所有点权加W
c. 对LCA(x, y)到根节点路径上所有点权值减W
d. 对LCA(x,y)的父节点 fa(LCA(x, y))到根节点路径上所有权值减W
于是要进行四次这样从一个点到根节点的区间修改.将问题进一步简化, 进行一个点X到根节点的区间修改。查询其他一点Y时,只有X在Y的子树内, X对Y的值才有贡献且贡献值为W.当单点更新X时,X实现了对X到根的路径上所有点贡献了W.于是只需要更新四个点(单点更新) ,查询一个点的子树内所有点权的和(区间求和)即可.
修改树链x,y等价于$ add(in[x],v),add(in[y],v),add(in[lca(x,y)],-v),add(in[fa(lca(x,y))],-v)$
应用1--3
树链修改,子树和查询
树链修改部分同上一问
当修改某个节点A, 查询另一节点B时
只有A在B的子树内, B的值会增加
W * (dep[A] - dep[B] + 1) => W * (dep [A] + 1) - W * dep[B]
那么我们用两个树状数组就可以实现:
第一个维护 ∑w[i]*(deep[i]+1) 支持单点修改,区间和查询。
第二个维护∑ w[i]:支持单点修改,区间和查询。
每次查询结果为Sum1(R[B]) – Sum1(L[B]-1) - (Sum2(R[B]) – Sum2(L[B]-1)) * dep [B].
应用1--4
单点更新,树链和查询
树链和查询与树链修改类似
修改节点x权值,当且仅当y是x的子孙节点时,x对y的值有贡献。
差分前缀和,y的权值等于dfs中[1,in[y]]的区间和。
单点修改:add(in[x],v),add(out[x]+1,-v);
应用1--5
子树修改,单点查询
修改节点x的子树权值,在dfs序上就是区间修改。
区间修改,单点查询
应用1--6
子树修改,子树和查询
区间修改,区间查询
应用1--7
子树修改,树链查询
树链查询同上,等价为根节点到y节点的链上所有节点和问题。
修改节点x的子树权值,当且仅当y是x的子孙节点时(或y等于x),x对y的值有贡献。
x对根节点到y节点的链上所有节点和的贡献为:
\(w[x]*(dep[y]-dep[x]+1)=w[x]*dep[y]-w[x]*(1-dep[x])\)
同问题三,用两个树状数组或线段树即可。
应用2--1
1、求LCA
两点第一次出现的位置之间的区间中,深度最小点就是LCA。这可以用RMQ解决。
2、求某个子树的权值和。
只在每个点第一次出现的位置做加法,查询某点就只需要查询该点在欧拉序中最后出现的位置的前缀减去第一次出现的位置-1的前缀和即可。
bfs序
1.在BFS序中,与点a相邻的下一个点可能有三种身份:(1)节点a的孩子 (2)节点a的第一后兄弟 (3)节点a的兄弟的孩子
2.在DFS序中,与点a相邻的下一个点可能有三种身份:(1)节点a的孩子(2)节点a的第一后兄弟(3)啥也不是(意思是说直接回到父辈及以上了)
q.push(1);
while (!q.empty()){
int x=q.front();
q.pop();
for (int i=0;i<g[x].size();i++) {
int y=g[x][i];
if (y==fa[x]) continue;
q.push(y);
}
}