DFS序

作者:weeping
出处:https://www.cnblogs.com/weeping/p/6847112.html
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

注:由于未找到相关题目,我自己敲的代码不确定正确性,我只自己测了自己小样例。(事后加上)

定义:dfs序:每个节点在dfs深度优先遍历中的进出栈的时间序列

定义两个数组,in[x],ou[x]。dfs从根结点开始,每个结点分别记录两个信息:in[x],ou[x],in[x]为dfs进入结点x时的时间戳,ou[x]为dfs离开结点x时的时间戳 ,这样子  in[x] --- ou[x] 区间表示以 x 为根的子树

void dfs(int u,int fa){
    in[u]=++time;
    num[time]=u;
    dep[u]=dep[fa]+1;
    for(int i=0;i<E[u].size();i++){
        int v=E[u][i];
        if(v==fa)continue;
        dfs(v,u);
    }
    ou[u]=time;
}

性质:dfs序可以把一棵树区间化,即可以求出每个节点的管辖区间。

对于一棵树的dfs序而言,同一棵子树所对应的一定是dfs序中连续的一段。


dfs序的七个基本问题:

1.点修改,子树和查询

在dfs序中,子树处于一个连续区间中。所以这题可以转化为:点修改,区间查询。用树状数组或线段树即可。

例题:poj3321 Apple Tree

#include<iostream>
#include<vector>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;
const int INF=1e9;
const int mod=1e9+7;
const int N=1e5+5;
#define LL long long
int in[N],ou[N],num[N],time;
vector<vector<int> >E(N);//POJ直接用vector超时
void dfs(int u,int fa){
    in[u]=++time;
    num[time]=u;
    int v;
    for(int i=0;i<E[u].size();i++)
        if((v=E[u][i])!=fa)
            dfs(v,u);
    ou[u]=time;
}
int val[N],n;
inline int lowbit(int x){
    return x&(-x);
}
void add(int x,int v){
    for(int i=x;i<=n;i+=lowbit(i))
        val[i]+=v;
}
int sum(int x){
    int ans=0;
    for(int i=x;i;i-=lowbit(i))
        ans+=val[i];
    return ans;
}
bool vis[N];
int main(){
    cin>>n;
    for(int i=1;i<n;i++){
        int u,v;
        scanf("%d%d",&u,&v);
        E[u].push_back(v);
        E[v].push_back(u);
    }
    dfs(1,0);
    for(int i=1;i<=n;i++)
        add(i,1),vis[i]=1;
    int q;cin>>q;
    while(q--){
        char ch;
        int x;
        getchar();
        scanf("%c %d",&ch,&x);
        if(ch=='Q')
            printf("%d\n",sum(ou[x])-sum(in[x]-1));
        else{
            if(vis[x]==1)
                add(in[x],-1);
            else
                add(in[x],1);
            vis[x]=!vis[x];
        }
    }
    return 0;
}

2.树链修改,单点查询

将一条树链x,y上的所有点的权值加v。这个问题可以等价为:

1).x到根节点的链上所有节点权值加v。

2).y到根节点的链上所有节点权值加v。

3).lca(x,y)到根节点的链上所有节点权值和减v。

4).fa(lca(x,y))到根节点的链上所有节点权值和减v。

上面四个操作可以归结为:节点x到根节点链上所有节点的权值加减v。修改节点x权值,当且仅当y是x的祖先节点时,x对y的值有贡献。

所以节点y的权值可以转化为节点y的子树节点贡献和。从贡献和的角度想:这就是点修改,区间和查询问题。

修改树链x,y等价于add(l[x],v),add(l[y],v),add(l[lca(x,y)],-v),add(l[fa(lca(x,y))],-v)。

查询:get_sum(r[x])-get_sum(l[x]-1)

用树状数组或线段树即可。
 

#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N=1e5+5;
//树状数组部分
int val[N],n;
inline int lowbit(int x){
    return x&(-x);
}
void add(int x,int v){
    if(x==0)return;
    for(int i=x;i<=n;i+=lowbit(i))
        val[i]+=v;
}
int sum(int x){
    int ans=0;
    for(int i=x;i;i-=lowbit(i))
        ans+=val[i];
    return ans;
}

//DFS序部分 + LCA
int in[N],ou[N],num[N],tim;
int dep[N],f[N][22];
vector<vector<int>>E(N);
void dfs(int u,int fa){
    in[u]=++tim;
    num[tim]=u;
    //初始化f数组
    dep[u]=dep[fa]+1;
    f[u][0]=fa;
    for(int i=1;i<=20;i++)
        f[u][i]=f[f[u][i-1]][i-1];

    for(int i=0;i<E[u].size();i++){
        int v=E[u][i];
        if(v==fa)continue;
        dfs(v,u);
    }
    ou[u]=tim;
}

int lca(int x,int y){
    if(dep[x]<dep[y])swap(x,y);
    int d=dep[x]-dep[y];
    for(int i=0;i<20;i++)
        if((1<<i)&d)
            x=f[x][i];
    if(x==y)return x;
    for(int i=20;i>=0;i--)
        if(f[x][i]!=f[y][i]){
            x=f[x][i];
            y=f[y][i];
        }
    return f[x][0];
}

int main(){
    cin>>n;
    for(int i=1;i<n;i++){
        int u,v;
        scanf("%d%d",&u,&v);
        E[u].push_back(v);
        E[v].push_back(u);
    }
    dfs(1,0);       //假设以1为根
    int q;cin>>q;
    while(q--){     //树链修改,单点求值
        char ch;
        int x,y,v;
        getchar();
        scanf("%c%d",&ch,&x);
        if(ch=='Q')
            printf("%d\n",sum(ou[x])-sum(in[x]-1));
        else if(ch=='C'){
            scanf("%d%d",&y,&v);
            add(in[x],v);
            add(in[y],v);
            int fxy=lca(x,y);
            add(in[fxy],-v);
            add(in[f[fxy][0]],-v);
        }
    }
    return 0;
}
/*给出一组数据
7
1 3
1 4
3 5
3 7
4 6
4 2
7
C 5 7 2
Q 3
C 5 2 3
Q 3
Q 4
Q 5
Q 1
*/

3.树链修改,子树和查询

树链修改部分同上一问题。下面考虑子树和查询问题:前一问是从贡献的角度想,子树和同理。

对于节点y其到根节点的权值和,考虑其子节点x的贡献:w[x]*(deep[x]-deep[y]+1) = w[x]*(deep[x]+1)-w[x]*deep[y]

所以节点y的子树和为:
 

       ps:公式中的v[i]为手误,应为w[i]。

  所以用两个树状数组或线段树即可:

    第一个维护∑w[i]*(deep[i]+1):支持操作单点修改,区间和查询。(这也就是问题2)

    第二个维护∑ w[i]:支持操作单点修改,区间查询。(这其实也是问题2)

#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N=1e5+5;
//树状数组部分
int val1[N],val2[N],n;
inline int lowbit(int x){
    return x&(-x);
}
void add(int x,int v,int *val){
    if(x==0)return;
    for(int i=x;i<=n;i+=lowbit(i))
        val[i]+=v;
}
int sum(int x,int *val){
    int ans=0;
    for(int i=x;i;i-=lowbit(i))
        ans+=val[i];
    return ans;
}

//DFS序部分 + LCA
int in[N],ou[N],num[N],tim;
int dep[N],f[N][22];
vector<vector<int>>E(N);
void dfs(int u,int fa){
    in[u]=++tim;
    num[tim]=u;
    //初始化f数组
    dep[u]=dep[fa]+1;
    f[u][0]=fa;
    for(int i=1;i<=20;i++)
        f[u][i]=f[f[u][i-1]][i-1];

    for(int i=0;i<E[u].size();i++){
        int v=E[u][i];
        if(v==fa)continue;
        dfs(v,u);
    }
    ou[u]=tim;
}

int lca(int x,int y){
    if(dep[x]<dep[y])swap(x,y);
    int d=dep[x]-dep[y];
    for(int i=0;i<20;i++)
        if((1<<i)&d)
            x=f[x][i];
    if(x==y)return x;
    for(int i=20;i>=0;i--)
        if(f[x][i]!=f[y][i]){
            x=f[x][i];
            y=f[y][i];
        }
    return f[x][0];
}

int main(){
    cin>>n;
    for(int i=1;i<n;i++){
        int u,v;
        scanf("%d%d",&u,&v);
        E[u].push_back(v);
        E[v].push_back(u);
    }
    dfs(1,0);       //假设以1为根
    int q;cin>>q;
    while(q--){     //树链修改,单点求值
        char ch;
        int x,y,v;
        getchar();
        scanf("%c%d",&ch,&x);
        if(ch=='Q'){
            LL ans=sum(ou[x],val1)-sum(in[x]-1,val1);
            ans-=dep[x]*(sum(ou[x],val2)-sum(in[x]-1,val2));
            printf("%lld\n",ans);
        }
        else if(ch=='C'){
            scanf("%d%d",&y,&v);
            int fxy=lca(x,y);
            //Val1
            add(in[x],v*(dep[x]+1),val1);
            add(in[y],v*(dep[y]+1),val1);
            add(in[fxy],-v*(dep[fxy]+1),val1);
            add(in[f[fxy][0]],-v*(dep[f[fxy][0]]+1),val1);
            //Val2
            add(in[x],v,val2);
            add(in[y],v,val2);
            add(in[fxy],-v,val2);
            add(in[f[fxy][0]],-v,val2);
        }
    }
    return 0;
}
/*给出一组数据
7
1 3
1 4
3 5
3 7
4 6
4 2
7
C 5 7 2
Q 3
C 5 2 3
Q 3
Q 4
Q 5
Q 1
*/

4.单点更新,树链和查询

树链和查询与树链修改类似,树链和(x,y)等于下面四个部分和相加:

1).x到根节点的链上所有节点权值加。

2).y到根节点的链上所有节点权值加。

3).lca(x,y)到根节点的链上所有节点权值和的-1倍。

4).fa(lca(x,y))到根节点的链上所有节点权值和的-1倍。

所以问题转化为:查询点x到根节点的链上的所有节点权值和。

修改节点x权值,当且仅当y是x的子孙节点时,x对y的值有贡献。

差分前缀和,y的权值等于dfs中[1,l[y]]的区间和。(不会树状数组进行该操作的可以参考:https://www.cnblogs.com/kickit/p/9172189.html

单点修改:add(l[x],v),add(r[x]+1,-v);
 

#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N=1e5+5;
//树状数组部分
int val[N],n;
inline int lowbit(int x){
    return x&(-x);
}
void add(int x,int v,int *val){
    if(x==0)return;
    for(int i=x;i<=n;i+=lowbit(i))
        val[i]+=v;
}
int sum(int x,int *val){
    int ans=0;
    for(int i=x;i;i-=lowbit(i))
        ans+=val[i];
    return ans;
}

//DFS序部分 + LCA
int in[N],ou[N],num[N],tim;
int dep[N],f[N][22];
vector<vector<int>>E(N);
void dfs(int u,int fa){
    in[u]=++tim;
    num[tim]=u;
    //初始化f数组
    dep[u]=dep[fa]+1;
    f[u][0]=fa;
    for(int i=1;i<=20;i++)
        f[u][i]=f[f[u][i-1]][i-1];

    for(int i=0;i<E[u].size();i++){
        int v=E[u][i];
        if(v==fa)continue;
        dfs(v,u);
    }
    ou[u]=tim;
}

int lca(int x,int y){
    if(dep[x]<dep[y])swap(x,y);
    int d=dep[x]-dep[y];
    for(int i=0;i<20;i++)
        if((1<<i)&d)
            x=f[x][i];
    if(x==y)return x;
    for(int i=20;i>=0;i--)
        if(f[x][i]!=f[y][i]){
            x=f[x][i];
            y=f[y][i];
        }
    return f[x][0];
}

int main(){
    cin>>n;
    for(int i=1;i<n;i++){
        int u,v;
        scanf("%d%d",&u,&v);
        E[u].push_back(v);
        E[v].push_back(u);
    }
    dfs(1,0);       //假设以1为根
    int q;cin>>q;
    while(q--){     //树链修改,单点求值
        char ch;
        int x,y,v;
        getchar();
        scanf("%c%d",&ch,&x);
        if(ch=='Q'){
            scanf("%d",&y);
            LL ans =sum(in[x],val);
               ans+=sum(in[y],val);
               int fxy=lca(x,y);
               ans-=sum(in[fxy],val);
               ans-=sum(in[f[fxy][0]],val);
            printf("%lld\n",ans);
        }
        else if(ch=='C'){
            scanf("%d",&v);
            add(in[x],v,val);
            add(ou[x]+1,-v,val);
        }
    }
    return 0;
}
/**给出一组数据
7
1 3
1 4
3 5
3 7
4 6
4 2
8
Q 1 2
C 3 2
Q 5 7
C 4 1
Q 5 2
C 1 1
Q 7 2
Q 1 1
*/

5.子树修改,单点查询

  修改节点x的子树权值,在dfs序上就是区间修改,单点权值查询就是单点查询。

  区间修改,单点查询问题:树状数组或线段树即可;

 (不会树状数组进行该操作的可以参考:https://www.cnblogs.com/kickit/p/9172189.html

#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N=1e5+5;
//树状数组部分
int val[N],n;
inline int lowbit(int x){
    return x&(-x);
}
void add(int x,int v,int *val){
    if(x==0)return;
    for(int i=x;i<=n;i+=lowbit(i))
        val[i]+=v;
}
int sum(int x,int *val){
    int ans=0;
    for(int i=x;i;i-=lowbit(i))
        ans+=val[i];
    return ans;
}

//DFS序部分 + LCA
int in[N],ou[N],num[N],tim;
int dep[N],f[N][22];
vector<vector<int>>E(N);
void dfs(int u,int fa){
    in[u]=++tim;
    num[tim]=u;
    //初始化f数组
    dep[u]=dep[fa]+1;
    f[u][0]=fa;
    for(int i=1;i<=20;i++)
        f[u][i]=f[f[u][i-1]][i-1];

    for(int i=0;i<E[u].size();i++){
        int v=E[u][i];
        if(v==fa)continue;
        dfs(v,u);
    }
    ou[u]=tim;
}

int lca(int x,int y){
    if(dep[x]<dep[y])swap(x,y);
    int d=dep[x]-dep[y];
    for(int i=0;i<20;i++)
        if((1<<i)&d)
            x=f[x][i];
    if(x==y)return x;
    for(int i=20;i>=0;i--)
        if(f[x][i]!=f[y][i]){
            x=f[x][i];
            y=f[y][i];
        }
    return f[x][0];
}

int main(){
    cin>>n;
    for(int i=1;i<n;i++){
        int u,v;
        scanf("%d%d",&u,&v);
        E[u].push_back(v);
        E[v].push_back(u);
    }
    dfs(1,0);       //假设以1为根
    int q;cin>>q;
    while(q--){     //子树修改,单点求值
        char ch;
        int x,y,v;
        getchar();
        scanf("%c%d",&ch,&x);
        if(ch=='Q'){
            LL ans = sum(in[x],val);
            printf("%lld\n",ans);
        }
        else if(ch=='C'){
            scanf("%d",&v);
            add(in[x],v,val);
            add(ou[x]+1,-v,val);
        }
    }
    return 0;
}
/**给出一组数据
7
1 3
1 4
3 5
3 7
4 6
4 2
8
Q 1
C 3 2
Q 5
C 1 3
Q 7
Q 1
Q 3
Q 4
*/

6.子树修改,子树和查询

  题目等价与区间修改,区间查询问题。用树状数组或线段树即可。

   (不会树状数组进行该操作的可以参考:https://www.cnblogs.com/kickit/p/9172189.html

#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N=1e5+5;
//树状数组部分
int val1[N],val2[N],n;
inline int lowbit(int x){
    return x&(-x);
}
void add(int x,int v,int *val){
    if(x==0)return;
    for(int i=x;i<=n;i+=lowbit(i))
        val[i]+=v;
}
int sum(int x,int *val){
    int ans=0;
    for(int i=x;i;i-=lowbit(i))
        ans+=val[i];
    return ans;
}

//DFS序部分 + LCA
int in[N],ou[N],num[N],tim;
int dep[N],f[N][22];
vector<vector<int>>E(N);
void dfs(int u,int fa){
    in[u]=++tim;
    num[tim]=u;
    //初始化f数组
    dep[u]=dep[fa]+1;
    f[u][0]=fa;
    for(int i=1;i<=20;i++)
        f[u][i]=f[f[u][i-1]][i-1];

    for(int i=0;i<E[u].size();i++){
        int v=E[u][i];
        if(v==fa)continue;
        dfs(v,u);
    }
    ou[u]=tim;
}

int lca(int x,int y){
    if(dep[x]<dep[y])swap(x,y);
    int d=dep[x]-dep[y];
    for(int i=0;i<20;i++)
        if((1<<i)&d)
            x=f[x][i];
    if(x==y)return x;
    for(int i=20;i>=0;i--)
        if(f[x][i]!=f[y][i]){
            x=f[x][i];
            y=f[y][i];
        }
    return f[x][0];
}

int main(){
    cin>>n;
    for(int i=1;i<n;i++){
        int u,v;
        scanf("%d%d",&u,&v);
        E[u].push_back(v);
        E[v].push_back(u);
    }
    dfs(1,0);       //假设以1为根
    int q;cin>>q;
    while(q--){     //子树修改,子树和查询
        char ch;
        int x,y,v;
        getchar();
        scanf("%c%d",&ch,&x);
        if(ch=='Q'){
            LL ans = (ou[x]+1)*sum(ou[x],val1) - sum(ou[x],val2);
               ans-= (in[x])*sum(in[x]-1,val1) - sum(in[x]-1,val2);
            printf("%lld\n",ans);
        }
        else if(ch=='C'){
            scanf("%d",&v);
            add(in[x],v,val1);
            add(ou[x]+1,-v,val1);
            add(in[x],v*in[x],val2);
            add(ou[x]+1,-v*(ou[x]+1),val2);
        }
    }
    return 0;
}
/**给出一组数据
7
1 3
1 4
3 5
3 7
4 6
4 2
9
Q 1
C 3 2
Q 3
C 1 3
Q 7
Q 1
Q 3
Q 4
Q 2
*/

7.子树修改,树链查询

树链查询同上,等价为根节点到y节点的链上所有节点和问题。

修改节点x的子树权值,当且仅当y是x的子孙节点时(或y等于x),x对y的值有贡献。

x对根节点到y节点的链上所有节点和的贡献为:w[x]*(deep[y]-deep[x]+1)=w[x]*deep[y]+w[x]*(1-deep[x])

同问题三,用两个树状数组或线段树即可。

(不会树状数组进行该操作的可以参考:https://blog.csdn.net/qq_40695203/article/details/101075768

由于本人数据结构太差,我使用四个树状数组维护,前两个搞w[x]*deep[y],后两个搞w[x]*(1-deep[x])

 

#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N=1e5+5;
//树状数组部分
int val1[N],val2[N],val3[N],val4[N],s[N],n;//si是 1--i的dep的和 ,
inline int lowbit(int x){
    return x&(-x);
}
void add(int x,int v,int *val){
    if(x==0)return;
    for(int i=x;i<=n;i+=lowbit(i))
        val[i]+=v;
}
int sum(int x,int *val){
    if(x<=0)return 0;
    int ans=0;
    for(int i=x;i;i-=lowbit(i))
        ans+=val[i];
    return ans;
}

//DFS序部分 + LCA
int in[N],ou[N],num[N],tim;
int dep[N],f[N][22];
vector<vector<int>>E(N);
void dfs(int u,int fa){
    in[u]=++tim;
    num[tim]=u;
    //初始化f数组
    if(u)
    dep[u]=dep[fa]+1;
    f[u][0]=fa;
    for(int i=1;i<=20;i++)
        f[u][i]=f[f[u][i-1]][i-1];

    for(int i=0;i<E[u].size();i++){
        int v=E[u][i];
        if(v==fa)continue;
        dfs(v,u);
    }
    ou[u]=tim;
}

int lca(int x,int y){
    if(dep[x]<dep[y])swap(x,y);
    int d=dep[x]-dep[y];
    for(int i=0;i<20;i++)
        if((1<<i)&d)
            x=f[x][i];
    if(x==y)return x;
    for(int i=20;i>=0;i--)
        if(f[x][i]!=f[y][i]){
            x=f[x][i];
            y=f[y][i];
        }
    return f[x][0];
}

inline int get(int id){
    if(id<=0)return 0;
    return s[id]*sum(id,val1) - sum(id,val2) + (id*sum(id,val3) - sum(id,val4));
}
int main(){
    cin>>n;
    for(int i=1;i<n;i++){
        int u,v;
        scanf("%d%d",&u,&v);
        E[u].push_back(v);
        E[v].push_back(u);
    }
    dfs(1,0);       //假设以1为根
    for(int i=1;i<=n;i++)   //处理前缀和s数组
        s[i]=dep[num[i]]+s[i-1];
    for(int i=1;i<=n;i++)
        printf("%d ",dep[num[i]]);puts("");
    for(int i=1;i<=n;i++)
        printf("%d ",s[i]);puts("");
    int q;cin>>q;
    while(q--){     //
        int x,y,v;
        getchar();
        scanf("%c%d",&ch,&x);
        if(ch=='Q'){
            scanf("%d",&y);
            LL ans = get(in[x])-get(in[x]-1);
               ans+= get(in[y])-get(in[y]-1);
            int fxy= lca(x,y);
               ans-= get(in[fxy])-get(in[fxy]-1);
               ans-= get(in[f[fxy][0]])-get(in[f[fxy][0]]-1);
            printf("%lld\n",ans);
        }
        else if(ch=='C'){
            scanf("%d",&v);
            add(in[x],v,val1);
            add(ou[x]+1,-v,val1);
            add(in[x],v*s[in[x]-1],val2);
            add(ou[x]+1,-v*s[ou[x]],val2);

            int v1=v*(1-dep[x]);
            add(in[x],v1,val3);
            add(ou[x]+1,-v1,val3);
            add(in[x],v1*(in[x]-1),val4);
            add(ou[x]+1,-v1*ou[x], val4);
        }
    }
    return 0;
}
/**给出一组数据
7
1 3
1 4
3 5
3 7
4 6
4 2
9
C 3 2
Q 5 3
Q 7 5
C 1 3
Q 3 1
Q 1 1
Q 4 5
Q 5 7
Q 5 2
*/

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值