倍增、DFS序

本文介绍了两个关于树的算法问题:一是利用深度优先搜索(DFS)和祖先节点查询求解树中两点间路径上的最小值;二是通过DFS序实现树的有序化,维护区间和及点权和,支持单点修改和查询。这两个问题都涉及到树的遍历和区间操作,是树形结构常见的数据结构与算法应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

目录

路径最小值​

DFS序练习1

DFS序练习2


路径最小值

 

dfs,相当于一个初始化操作;

以及有一个跟ST表类似的预处理操作

query函数的总体思想就是,利用2个节点的深度来不断向上查最近公共祖先。(前提是2个节点处于同一深度,代码中有体现这一点,可能处于同一深度之后,u就是v的祖先,也可能不是,那就继续向上查找)

// problem :  

#include <bits/stdc++.h>
using namespace std;
#define ll long long
typedef pair<int, int> PII;
#define pb push_back
const int LOGN = 18;
const int N = 201000;
int n, q;
int dep[N], par[N][LOGN + 1], val[N][LOGN + 1];
std::vector<pair<int, int>> e[N];

int query(int u, int v) {
   int ans = 1 << 30;
   if(dep[u] > dep[v]) swap(u, v);
   int d = dep[v] - dep[u];
   for (int j = LOGN; j >= 0; --j) if (d & (1 << j)) {
      ans = min(ans, val[v][j]);
      v = par[v][j];
   }
   if (u == v) return ans;
   for (int j = LOGN; j >= 0; --j) if (par[u][j] != par[v][j]) {
      ans = min(ans, min(val[u][j], val[v][j]));
      u = par[u][j];
      v = par[v][j];
   }
   ans = min(ans, min(val[u][0], val[v][0]));
   return ans;
}
void dfs(int u, int f) {
   dep[u] = dep[f] + 1;
   for (auto p : e[u]) {
      int v = p.first;
      if (v == f) continue;
      par[v][0] = u;
      val[v][0] = p.second;
      dfs(v, u);
   }
}
int main(){
   scanf("%d %d", &n, &q);
   for (int i = 1; i < n; ++i) {
      int u, v, w;
      scanf("%d %d %d", &u, &v, &w);
      e[u].push_back(make_pair(v, w));
      e[v].push_back(make_pair(u, w));
   }
   dfs(1, 0);
   for (int j = 1; j <= LOGN; ++j) {
      for (int u = 1; u <= n; ++u) {
         par[u][j] = par[par[u][j - 1]][j - 1];
         val[u][j] = min(val[u][j - 1], val[par[u][j - 1]][j - 1]);
      }
   }

   for (int i = 1; i <= q; ++i) {
      int u, v;
      scanf("%d %d", &u, &v);
      printf("%d\n", query(u, v));
   }

   return 0;
}

DFS序练习1

DFS序,   将一棵树有序化,特点是子树的序号是连续的,(子树 相当于 涉及区间问题)

c1  维护区间和   

c2 维护点x到根的点权和     (差分) 

为什么为想到差分呢? 可以知道,点x的孩子要到根的话,那么一定会经过点x,所以要加上x这个点的点权, 所以是区间加,并且是单点查询,很自然就想到差分树状数组了。

// problem :  DFS序
#include <bits/stdc++.h>
using namespace std;
#define ll long long
typedef pair<int, int> PII;
#define pb push_back
const int N = 201000;
int n, q, l[N], r[N], tot, a[N];
std::vector<int> e[N];

template<class T>
struct BIT{
    T c[N];
    int size;
    void init(int n){
        size = n;
        for (int i = 1; i <= n; ++i) c[i] = 0;
    }
    inline void modify(int x, T d){
        while(x <= size){
            c[x] += d;
            x += x & -x;
        }
    }
    inline T query(int x){
        T res = 0;
        while(x){
            res += c[x];
            x -= x & -x;
        }
        return res;
    }
};
BIT<ll> c1, c2;
void dfs(int u, int f) {
    l[u] = ++tot;
    for (auto v : e[u]) {
        if (v == f) continue;
        dfs(v, u);
    }
    r[u] = tot;
}
int main(){
    scanf("%d %d", &n, &q);
    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);
    c1.init(n);
    c2.init(n);
    for (int i = 1; i <= n; ++i) {
        scanf("%d", &a[i]);
        c1.modify(l[i], a[i]);
        c2.modify(l[i], a[i]);
        c2.modify(r[i] + 1, -a[i]);
    }

    for (int i = 1; i <= q; ++i) {
        int ty;
        scanf("%d", &ty);
        if (ty == 1) {
            int x, y;
            scanf("%d %d", &x, &y);
            int d = y - a[x];
            a[x] = y;
            c1.modify(l[x], d);
            c2.modify(l[x], d);
            c2.modify(r[x] + 1, -d);
        } else {
            int x;
            scanf("%d", &x);
            printf("%lld %lld\n", c1.query(r[x]) - c1.query(l[x] - 1), c2.query(l[x]));
        }
    }

    return 0;
}

DFS序练习2

 这题跟上一题没有区别,唯一变动的地方就是多了个换根操作,这也是难点之一。

我们一旦换了根,那我们原先的DFS序就会被打乱。这里的解决方法是不进行真正的换根操作,而进行逻辑上的换根。当我们查询x点子树的点权和时,通过x和root之间的关系,来进行模拟换根处理。

这里x和root之间有3种关系:

        1、x 就是 root          ---->  query(n)

        2、root是x的子孙     整体减去 x 的包含root区间的那个子区间

        3、其他                   还是正常的query(r[x]) - query(l[x] - 1)

第1种情况很明显,不画图演示。

第2种情况: 

 第3种情况:

// problem :  DFS序
#include <bits/stdc++.h>
using namespace std;
#define ll long long
typedef pair<int, int> PII;
#define pb push_back
const int N = 201000;
int n, q, l[N], r[N], tot, a[N];
std::vector<int> e[N];
vector<PII> son[N];
template<class T>
struct BIT{
    T c[N];
    int size;
    void init(int n){
        size = n;
        for (int i = 1; i <= n; ++i) c[i] = 0;
    }
    inline void modify(int x, T d){
        while(x <= size){
            c[x] += d;
            x += x & -x;
        }
    }
    inline T query(int x){
        T res = 0;
        while(x){
            res += c[x];
            x -= x & -x;
        }
        return res;
    }
};
BIT<ll> c1;
void dfs(int u, int f) {
    l[u] = ++tot;
    for (auto v : e[u]) {
        if (v == f) continue;
        dfs(v, u);
        son[u].push_back(make_pair(l[v], r[v]));
    }
    r[u] = tot;
}
int main(){
    scanf("%d %d", &n, &q);
    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);
    c1.init(n);
    for (int i = 1; i <= n; ++i) {
        scanf("%d", &a[i]);
        c1.modify(l[i], a[i]);
    }
    int root = 1;
    for (int i = 1; i <= q; ++i) {
        int ty;
        scanf("%d", &ty);
        if (ty == 1) {
            int x, y;
            scanf("%d %d", &x, &y);
            int d = y - a[x];
            a[x] = y;
            c1.modify(l[x], d);
        } else if (ty == 3){
            scanf("%d", &root);

        } else {
            int x;
            scanf("%d", &x);
            if (x == root) {
                printf("%lld\n", c1.query(n));
            } else if (l[x] < l[root] && r[x] >= r[root]) {
                auto seg = *prev(upper_bound(son[x].begin(), son[x].end(), 
                    PII {l[root], r[root]}));
                printf("%lld\n", c1.query(n) - (c1.query(seg.second) - c1.query(seg.first - 1)));
            } else {
                printf("%lld\n", c1.query(r[x]) - c1.query(l[x] - 1));
            }
        }
    }

    return 0;
}

#include<bits/stdc++.h> #define ull unsigned long long #define ll long long #define pb push_back #define mkp make_pair #define fi first #define se second #define inf 1000000000 #define infll 1000000000000000000ll #define pii pair<int,int> #define rep(i,a,b,c) for(int i=(a);i<=(b);i+=(c)) #define per(i,a,b,c) for(int i=(a);i>=(b);i-=(c)) #define F(i,a,b) for(int i=a,i##end=b;i<=i##end;i++) #define dF(i,a,b) for(int i=a,i##end=b;i>=i##end;i--) #define cmh(sjy) while(sjy--) #define lowbit(x) (x&(-x)) #define HH printf("\n") #define eb emplace_back #define poly vector<int> #define SZ(x) ((int)x.size() using namespace std; // 快速取模幂运算 template<typename T>inline void chkmax(T &x,const T &y){ x=std::max(x,y); } template<typename T>inline void chkmin(T &x,const T &y){ x=std::min(x,y); } const int mod=998244353, maxn=100005; void fre(){ freopen("mountain.in","r",stdin),freopen("mountain.out","w",stdout); } // 快速幂函数 inline int qpow(int x,ll y){ int rt=1; for(;y;y>>=1,x=1ll*x*x%mod) if(y&1) rt=1ll*rt*x%mod; return rt; } // 加法、减法、乘法的封装,带模数处理 inline void inc(int &x,const int y){ x=(x+y>=mod)?(x+y-mod):(x+y); } inline void dec(int &x,const int y){ x=(x>=y)?(x-y):(x+mod-y); } inline void mul(int &x,const int y){ x=1ll*x*y%mod; } inline int add(const int x,const int y){ return (x+y>=mod)?(x+y-mod):(x+y); } inline int sub(const int x,const int y){ return (x>=y)?(x-y):(x+mod-y); } inline int prod(const int x,const int y){ return 1ll*x*y%mod; } int n, dfn[maxn], siz[maxn], tim, stk[maxn], top, dep[maxn], f[maxn], fa[maxn]; int anc[maxn][20], mn[maxn][20]; // 倍增祖先数组 int ul[maxn], ur[maxn], uh[maxn], lim[maxn], pre[maxn], fr[maxn], tol[maxn], tor[maxn], diff[maxn], th[maxn]; vector<int>g[maxn], tmp, nxt[maxn]; // 子树结构 vector<pii>qu[maxn]; // 查询队列 // 第一次DFS:预处理树结构、深度、倍增表等 void dfs1(int u){ dfn[u]=++tim, siz[u]=1; stk[top++]=u; tmp.push_back(u); // 找到某个祖先节点 v,满足其限制条件 auto find=[&](int k){ return k>dep[u]?0:stk[dep[u]-k]; }; int v=u; dF(i,18,0) if(anc[v][i]&&mn[v][i]>=lim[u]) v=anc[v][i]; fr[u]=fa[v], nxt[fr[u]].push_back(u); // 处理冲刺区间 [l_i, r_i] const int L=dep[u]-ul[u]+1, R=dep[u]-ur[u]; // 获取当前节点的“休息目标” th[u]=find(uh[u]+1); if(lim[u]>=R){ int p=u,to=find(ur[u]+1); dF(i,18,0) if(anc[p][i]&&mn[p][i]>=R)p=anc[p][i]; qu[to].push_back(mkp(u,mod-1)), qu[to].push_back(mkp(fa[p],1)); inc(diff[fa[p]],mod-1); }else inc(diff[u],mod-1); if(lim[u]>=L){ int p=u,to=find(ul[u]); dF(i,18,0) if(anc[p][i]&&mn[p][i]>=L)p=anc[p][i]; qu[to].push_back(mkp(u,1)), qu[to].push_back(mkp(fa[p],mod-1)); inc(diff[fa[p]],1); }else inc(diff[u],1); // 遍历子树 for(int v:g[u])dfs1(v),siz[u]+=siz[v]; --top; } // 第三次DFS:进行差分标记合并 void dfs3(int u){ for(int v:nxt[u])dfs3(v),inc(diff[u],diff[v]); if(!u)return; qu[th[u]].push_back(mkp(u,diff[u])); qu[th[u]].push_back(mkp(fr[u],sub(0,diff[u]))); } // 树状数组实现前缀和查询 namespace DS{ int t[maxn]; void init(){ memset(t,0,sizeof t); } void add(int x,int y){ if(x)for(;x<=n;x+=lowbit(x))inc(t[x],y); } int query(int x){ int R=0;for(;x;x^=lowbit(x))inc(R,t[x]);return R; } int query(int l,int r){ return l>r?0:sub(query(r),query(l-1)); } } // 第二次DFS:最终DP计算 void dfs2(int u){ f[u]=(u==1)?1:DS::query(dfn[u],dfn[u]+siz[u]-1); pre[u]=add(pre[fa[u]],f[u]); // 将查询结果更新进树状数组 for(auto [x,y]:qu[u])DS::add(dfn[x],1ll*y*pre[u]%mod); // 遍历子树 for(int v:g[u])dfs2(v); } // 主处理函数:读入数据并调用各部分 void solve(){ cin>>n, tim=top=0, dep[0]=-1; F(i,0,n) g[i].clear(), fr[i]=diff[i]=0, qu[i].clear(), nxt[i].clear(); F(i,2,n) cin>>fa[i]>>ul[i]>>ur[i]>>uh[i], dep[i]=dep[fa[i]]+1, lim[i]=dep[i]-uh[i]-1; // 构建图结构 F(i,2,n) g[fa[i]].push_back(i); // 倍增表预处理 F(i,1,n) anc[i][0]=fa[i], mn[i][0]=lim[fa[i]]; F(j,1,18) F(i,1,n) anc[i][j]=anc[anc[i][j-1]][j-1], mn[i][j]=min(mn[i][j-1], mn[anc[i][j-1]][j-1]); // DFS遍历 dfs1(1), dfs3(0); DS::init(); // DP计算 dfs2(1); // 输出答案 F(i,2,n) cout<<f[i]<<' '; cout<<endl; } int testid; // 主函数 signed main(){ fre(), ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int zsy=1; cin>>testid>>zsy; F(____,1,zsy)solve(); // 多组测试数据循环 } 这份代码请给dfs1里面再添加一些注释
07-04
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xingxg.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值