首页 > 其他分享 >虚树

虚树

时间:2024-03-26 21:25:41浏览次数:27  
标签:int top tp st dep 虚树 hd

虚树

用途

  • 一棵树上进行 m 次不同的操作, 每次用到 k 个节点( $\sum k $ $ O(n) 级别$ )
  • 用于于树上 DP

原理

  • 将原树里的一部分用到的节点抠出来, 建一棵新树(虚树), 在上面进行 DP
    • 优点: 降低每次操作的复杂度

构建

  • 将要用到的节点(设为 s)按照 dfn 序排序
    • dfn 序相近的在原树上的位置一定是相邻的
  • 把树分成几条链来构建
    • 链的终点是当前链最深的 s 节点
      • 比如下图中 1, 9, 8 是 s 节点, 那么就会有(1, 5, 7, 8)(1, 9)两条链
  • 还要多加一些节点(相邻两点的 lca)
    • 比如 1, 6, 10 节点是我要用到的 s 节点
    • 但是 6 和 10 不在同一条链上, 把他们直接连在 1 上是不合理的, 所以引入他们的 lca 也就是 1 节点
  • 类似于笛卡尔树, 我们用栈来维护链

过程

  • 栈叫 st, 栈顶元素的下标为 top

  • 初始时将第一个 s 节点入栈

  • 当前节点为 now , 他和 st[top] 的 lca 叫 Lca

  • 考虑 now, st[top], st[top - 1] 的关系

    • 图来自Rhodoks 的洛谷博客

    • Lca == st[top]

      • now 和 st[top] 在同一条链上, 说明这条链还没处理完, 将 now 入栈即可

    • Lca 在 st[top], st[top - 1] 之间

      • 说明 st[top] 这条链处理完了, 将 st[top] 和 Lca 连边, 然后出栈
      • 将 Lca, now 入栈
    • Lca == st[top - 1]

      • 与情况 2 类似, 只是不必再把 Lca 入栈一次
    • Lca 在 st[top - 1] 上面

      • 同样是 st[top] 这条链处理完了, 只不过往上返回的层数多了点

      • st[top], st[top - 1] 连边, While 循环往上跳, 重复操作, 直到遇到 1, 2, 3 的情况然后退出即可

复杂度

  • \(\sum k * log n(LCA) + nlogn(排序)\)

例题

P2495 [SDOI2011] 消耗战

  • 每次对这 k 个岛建虚树即可

  • 关于 Dp

    • 考虑断开 x 子树内所有关键节点的代价
    • 如果 x 是关键点
      • 当前的断开的代价是他到根节点路径上的最小边权
      • 没必要加上 x 子树里关键点断开的代价(断开 x 时整棵子树都掉下来了)
    • 否则
      • 代价为 $ min(他到根节点路径上的最小边权, x 子树里关键点断开的代价) $
  • 代码

    • # include <bits/stdc++.h>
      # define int long long
      using namespace std;
      const int M = 1e6 + 10;
      const int N = 3e5 + 10;
      
      int n, m;
      int u, v, w;
      int k, h[N];
      int mini[N], tag[N];
      int dfn[N], si[N], son[N], tp[N], fa[N], dep[N], cdfn;
      int st[N], top;
      
      struct Add_edge{
          struct Edge1{
              int to, val, nxt;
          }e[M];
          int hd[N], cnt;
      
          void Insert(int u, int v, int w){
              e[++cnt].to = v;
              e[cnt].val = w;
              e[cnt].nxt = hd[u];
              hd[u] = cnt;
          }
      }a, b;
      
      bool cmp(int x, int y){
          return dfn[x] < dfn[y];
      }
      
      void Dfs1(int x, int y){
          fa[x] = y;
          si[x] = 1;
          dep[x] = dep[y] + 1;
          dfn[x] = ++cdfn;
          for(int i = a.hd[x]; i; i = a.e[i].nxt){
              int to = a.e[i].to;
              if(to == y){
                  continue;
              }
              mini[to] = min(mini[x], a.e[i].val);
              Dfs1(to, x);
              si[x] += si[to];
              if(si[son[x]] < si[to]){
                  son[x] = to;
              }
          }
      }
      
      void Dfs2(int x, int top){
          tp[x] = top;
          if(son[x]){
              Dfs2(son[x], top);
          }
          for(int i = a.hd[x]; i; i = a.e[i].nxt){
              int to = a.e[i].to;
              if(tp[to]){
                  continue;
              }
              Dfs2(to, to);
          }
      }
      
      int Lca(int x, int y){
          while(tp[x] != tp[y]){
              if(dep[tp[x]] < dep[tp[y]]){
                  swap(x, y);
              }
              x = fa[tp[x]];
          }
          if(dep[x] > dep[y]){
              swap(x, y);
          }
          return x;
      }
      
      int Dp(int x, int y){
          int sum = 0;
          for(int i = b.hd[x]; i; i = b.e[i].nxt){
              int to = b.e[i].to;
              if(to == y){
                  continue;
              }
              sum += Dp(to, x);
          }
          int ret = 0;
          if(tag[x]){
              ret = mini[x];
          }else{
              ret = min(mini[x], sum);
          }
          tag[x] = 0;
          b.hd[x] = 0;
          return ret;
      }
      
      signed main(){
          // freopen("1.in", "r", stdin);
          ios::sync_with_stdio(0);
          cin.tie(0); cout.tie(0);
      
          cin >> n;
          for(int i = 1; i < n; i++){
              cin >> u >> v >> w;
              a.Insert(u, v, w);
              a.Insert(v, u, w);
          }
          mini[1] = (int)1e18;
          Dfs1(1, 0);
          Dfs2(1, 1);
          cin >> m;
          for(int i = 1; i <= m; i++){
              cin >> k;
              for(int j = 1; j <= k; j++){
                  cin >> h[j];
                  tag[h[j]] = 1;
              }
              sort(h + 1, h + 1 + k, cmp);
      
              b.cnt = 0;
              top = 1;
              st[1] = h[1];
              for(int j = 2; j <= k; j++){
                  int lca = Lca(st[top], h[j]);
                  while(true){
                      if(dep[lca] >= dep[st[top - 1]]){
                          b.Insert(lca, st[top], 0);
                          if(lca != st[top - 1]){
                              st[top] = lca;
                          }else{
                              top--;
                          }
                          break;
                      }else{
                          b.Insert(st[top - 1], st[top], 0);
                          top--;
                      }
                  }
                  st[++top] = h[j];
              }
              while(--top){
                  b.Insert(st[top], st[top + 1], 0);
              }
              cout << Dp(st[1], 0) << "\n";
          }
      }
      

P4103 [HEOI2014] 大工程

  • 还是虚树, 不详写了吧

  • 代码

    • 特判一下, 如果 st[top] == Lca 那就别连边了

    • # include <bits/stdc++.h>
      # define int long long
      # define double long double
      using namespace std;
      const int N = (int)1e6 + 10;
      
      int n, q;
      int u, v;
      int k, id[N], tag[N];
      int fa[N], dep[N], si[N], son[N], dfn[N], top[N], cdfn;
      int st[N], tp;
      int f[N], g[N], aa, bb, cc, siz[N];
      
      struct Add_edge{
          struct Edge{
              int to, val, nxt;
          }e[2 * N];
          int hd[N], cnt;
      
          void Insert(int u, int v, int w){
              e[++cnt].to = v;
              e[cnt].val = w;
              e[cnt].nxt = hd[u];
              hd[u] = cnt;
          }
      }a, b;
      
      bool cmp(int x, int y){
          return dfn[x] < dfn[y];
      }
      
      void Dfs1(int x, int y){
          fa[x] = y;
          si[x] = 1;
          dfn[x] = ++cdfn;
          dep[x] = dep[y] + 1;
          for(int i = a.hd[x]; i; i = a.e[i].nxt){
              int to = a.e[i].to;
              if(to == y){
                  continue;
              }
              Dfs1(to, x);
              si[x] += si[to];
              if(si[son[x]] < si[to]){
                  son[x] = to;
              }
          }
      }
      
      void Dfs2(int x, int tp){
          top[x] = tp;
          if(son[x]){
              Dfs2(son[x], tp);
          }
          for(int i = a.hd[x]; i; i = a.e[i].nxt){
              int to = a.e[i].to;
              if(top[to]){
                  continue;
              }
              Dfs2(to, to);
          }
      }
      
      int Lca(int x, int y){
          while(top[x] != top[y]){
              if(dep[top[x]] < dep[top[y]]){
                  swap(x, y);
              }
              x = fa[top[x]];
          }
          if(dep[x] > dep[y]){
              swap(x, y);
          }
          return x;
      }
      
      void Dp(int x, int y){
          siz[x] = tag[x];
          f[x] = 0, g[x] = (tag[x] ? 0 : (int)1e18);
          for(int i = b.hd[x]; i; i = b.e[i].nxt){
              int to = b.e[i].to;
              if(to == y){
                  continue;
              }
              Dp(to, x);
              aa += (k - siz[to]) * siz[to] * b.e[i].val;
              if(siz[x]){
                  bb = max(bb, f[x] + f[to] + b.e[i].val);
                  cc = min(cc, g[x] + g[to] + b.e[i].val);
              }
              f[x] = max(f[x], f[to] + b.e[i].val);
              g[x] = min(g[x], g[to] + b.e[i].val);
              siz[x] += siz[to];
          }
          tag[x] = 0;
          b.hd[x] = 0;
      }
      
      signed main(){
          // freopen("1.in", "r", stdin);
          cin >> n;
          for(int i = 1; i < n; i++){
              cin >> u >> v;
              a.Insert(u, v, 1ll);
              a.Insert(v, u, 1ll);
          }
          Dfs1(1, 0);
          Dfs2(1, 1);
          cin >> q;
          for(int i = 1; i <= q; i++){
              cin >> k;
              for(int j = 1; j <= k; j++){
                  cin >> id[j];
                  tag[id[j]] = 1;
              }
              sort(id + 1, id + 1 + k, cmp);
              b.cnt = 0;
              tp = 1;
              st[1] = id[1];
              for(int j = 2; j <= k; j++){
                  int now = id[j];
                  int lca = Lca(st[tp], now);
                  while(true){
                      if(dep[lca] >= dep[st[tp - 1]]){
                          if(lca != st[tp])
                              b.Insert(lca, st[tp], dep[st[tp]] - dep[lca]);
                          if(st[tp - 1] == lca){
                              tp--;
                          }else{
                              st[tp] = lca;
                          }
                          break;
                      }else{
                          b.Insert(st[tp - 1], st[tp], dep[st[tp]] - dep[st[tp - 1]]);
                          tp--;
                      }
                  }
                  st[++tp] = now;
              }
              while(--tp){
                  b.Insert(st[tp], st[tp + 1], dep[st[tp + 1]] - dep[st[tp]]);
              }
              aa = 0, bb = 0, cc = (int)1e18;
              Dp(st[1], 0);
              cout << aa << " " << cc << " " << bb << "\n";
          }
      }
      

标签:int,top,tp,st,dep,虚树,hd
From: https://www.cnblogs.com/wangyangjena/p/18097593

相关文章

  • 虚树学习笔记
    虚树学习笔记定义虚树指的是不同于原树(我称之为实树)的重构树,使得在同样能够求解的情况下,整棵树的节点数更少,从而使得在存在多次询问时,一些复杂度关于树的节点数的树上算法能够在时限内求解。常用场景一般来说,虚树的使用场景比较单一,常见于在实树上存在一些特殊节点,并且答案与......
  • Educational Codeforces Round 162 E 点分治 虚树 树形dp
    传送门给出\(n\le2\cdot10^5\)的一棵树,每个节点有一个颜色。求出路径长度为\(2\)路径首端和尾端拥有相同颜色,且路径上其他点不存在相同颜色的点的路径条数。当时看错题了,把颜色抽出来后没法做了。后来感觉能点分治,然后把题看对了,遂写了一个极其抽象的点分治。除此之外,把某......
  • 虚树
    虚树主要是针对这一类问题:答案只跟选定的某些点(及它们的lca)有关,且选定点的总量可以接受而每次询问都搜索一遍整棵树很浪费因此建出虚树,在虚树上进行各种操作构建有两种方法:二次排序求lca,单调栈单调栈单调栈上维护的是虚树的一条链第一步肯定是将点按照dfn序排序为了......
  • 虚树学习笔记
    虚树学习笔记虚树,顾名思义,不是真实的树。在关于树的问题中,虚树起到缩小题目规模,优化算法的作用。算法思路引入P2495SDOI2011消耗战设\(dp[i]\)为\(i\)与所有该子树内资源丰富节点不联通的代价。如果\(u\)的儿子\(v\),不是资源丰富节点。\[dp[u]+=\min(w(u,v),dp[......
  • 虚树
    一种很有用的东西。体现了关键点的思想。应用1树上每个节点有颜色,问一个子树内有几种颜色。对每种颜色的点集按DFS序排序,点所在位置权值+1,相邻两个的LCA处权值-1。子树求和即可。正经的应用建虚树:q[hh=0]=1;for(i){ intl=lca(a[i],q[hh]); while(hh&&dep[q[hh]]>d......
  • [学习笔记]虚树
    虚树虚树可以应用于树形\(DP\)的加速。当题目规定查询点集的大小和\(\le10^5\)时可以用虚树解决。虚树的原理是在原树上重新建一棵树,使得树上只包含要询问的点和它们的\(lca\)。普通树形\(DP\)的时间复杂度为\(O(n^2)\)。最坏形成一棵二叉树,点集大小为\(n\),总点数为......
  • 虚树 学习笔记
    2023/10/6发现找不到题做了,决定学习新算法。经过在一些题单中的翻找,决定学习虚树。Part1.引入以一道例题来引入虚树吧。[HEOI2014]大工程给定一棵有\(n\)个点的树,边权均为\(1\)。现在有\(q\)次询问。每次询问取\(k\)个点出来建立完全图。定义连接两个点的代价为......
  • 【线段树合并、虚树】P5327 [ZJOI2019] 语言
    终于1kAC了家人,感动吧。贺了很久,很累。前置题目:P3320[SDOI2015]寻宝游戏虚树的边权和:\[\sumdep_{a_x}-\sum_{x<n}dep_{a_x,a_{x+1}}-dep_{a_{1},a_{n}}\]考虑转化贡献,求过该点的链的并,最后再除以二即可。那么我们可以考虑维护以该点的子树的所有关键点以及......
  • 【学习笔记】虚树
    点击查看目录目录定义构造虚树二次排序+LCA连边单调栈虚树,不是虚数\(i\)。定义在树形dp等题目中,树中点很少,可以直接跑dp。但是如果很大但是我们只需要查询很少的一些点呢?我们称某次询问的点为【关键点】。我们看上图,只有左边子树的两个节点被查询了,那右边的子树有......
  • 【学习笔记】(24) 虚树
    虚树常常被使用在树形dp中,当一次询问仅仅涉及到整颗树中少量结点时,为每次询问都对整棵树进行dp在时间上是不可接受的。此时,我们建立一颗仅仅包含部分关键结点的虚树,将非关键点构成的链简化成边或是剪去,在虚树上进行dp。虚树包含所有的询问点及它们之间的lca。显然虚树的叶子节点......