[Kattis Boxes] 倍增法LCA / DFS序

该博客主要介绍了Kattis Boxes问题,涉及N个箱子之间的包含关系,形成森林结构。博客讨论了如何通过DFS序和LCA(最近公共祖先)算法来高效回答关于箱子包含总数的查询。提供了两种解题思路,一种是利用DFS序将子树映射为区间,另一种是使用LCA进行节点排序和子树大小计算。并给出了参考代码。

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

[Kattis Boxes] 倍增法LCA / DFS序

题目链接【Virtual Judge】 【Kattis Boxes】
题目描述
There are N boxes, indexed by a number from 1 to N. Each box may (or not may not) be put into other boxes. These boxes together form a tree structure (or a forest structure, to be precise).

You have to answer a series of queries of the following form: given a list of indices of the boxes, find the total number of boxes that the list of boxes actually contain.

Consider, for example, the following five boxes.
Figure 1: Sample input

  1. If the query is the list “1”, then the correct answer is “5”, because box 1 contains all boxes.
  2. If the query is the list “4 5”, then the correct answer is “2”, for boxes 4 and 5 contain themselves and nothing else.
  3. If the query is the list “3 4”, then the correct answer is “2”.
  4. If the query is the list “2 3 4”, then the correct answer is “4”,
    since box 2 also contains box 5.
  5. If the query is the list “2”, then the correct answer is “3”,
    because box 2 contains itself and two other boxes.

Input

The first line contains the integer N(1N200000,1N200000) , the number of boxes.

The second line contains NN­integers. The iith integer is either the index of the box which contains the i th box, or zero if the iith box is not contained in any other box.

The third line contains an integer Q(1Q1000001Q100000), the number of queries. The following Q lines will have the following format: on each line, the first integer M(1M201M20) is the length of the list of boxes in this query, then M integers follow, representing the indices of the boxes.

Output

For each query, output a line which contains an integer representing the total number of boxes.
中文题意
N 个箱子,箱子可以装在另外一个箱子里面,可以不被其他箱子包含。然后有 Q 次询问,每次给定 M 个箱子,问这 M 个箱子里面一共包含了几个箱子(包括这 M 个箱子本身)。
解题思路:由于箱子与箱子之间是一对多的关系,那么这 N 个箱子可以看成一个森林(即多棵树)。然后有两种方法:

  • 比较简单的做法是先求出DFS序(时间戳),按DFS序编号,然后就能把子树对应到区间,那么,最后会得到 k 个区间,求区间并。

    • 另外一种比较zuo的做法是LCA搞。首先求出所有的树上节点对应的子树的大小 sz ,节点深度 dep ,以及LCA的初始化
      fa 。然后按照节点深度对 M 个节点按照节点排序。然后,对于 M 个点,LCA去重,依次加上节点为根节点的子树的大小即可。

    • 参考代码1

      /**
       * LCA倍增法
       */
      #include <bits/stdc++.h>
      
      using namespace std;
      
      const int MX = 200000 + 5;
      const int MM = 20 + 5;
      
      struct Edge {
          int v, next;
      } edge[MX];
      int head[MX], tot;
      int N, Q, M, buf[MM], IDU[MX];
      int ans;
      struct TNode {
          int sz, dep, root;
          int fa[MM];
      } d[MX];
      
      void init() {
          tot = 0;
          memset(head, -1, sizeof(head));
          memset(IDU, 0, sizeof(IDU));
      }
      void add_edge(int u, int v) {
          edge[tot] = Edge{v, head[u]};
          head[u] = tot ++;
      }
      
      void dfs(int u, int k, int& rt) {
          int v;
          d[u].sz = 1;
          d[u].dep = k;
          d[u].root = rt;
          for(int i = head[u]; ~i; i = edge[i].next) {
              v = edge[i].v;
              d[v].fa[0] = u;
              for(int j = 1; j < MM; j++) d[v].fa[j] = d[d[v].fa[j - 1]].fa[j - 1];
              dfs(v, k + 1, rt);
              d[u].sz += d[v].sz;
          }
      }
      bool cmp(const int& a, const int& b) {
          return d[a].dep < d[b].dep;
      }
      int LCA(int u, int v) {
          int i, j;
          if(d[u].dep < d[v].dep) swap(u, v);
          for(i = 0; (1 << i) <= d[u].dep; i ++);
          i--;
          for(j = i; j >= 0; j --) {
              if(d[u].dep - (1 << j) >= d[v].dep) u = d[u].fa[j];
          }
          if(u == v) return u;
          for(j = i; j >= 0; j --) {
              if(d[u].fa[j] != d[v].fa[j] && d[u].fa[j] != u) {
                  u = d[u].fa[j], v = d[v].fa[j];
              }
          }
          return d[u].fa[0];
      }
      bool chosen(const int& u, const int& v) {
          if(d[u].root != d[v].root) return false;
          int lca = LCA(u, v);
          if(u == lca || v == lca) return true;
          return false;
      }
      int main() {
          int u, v;
          while(~scanf("%d", &N)) {
              init();
              for(v = 1; v <= N; v ++) {
                  scanf("%d", &u);
                  if(u == 0) continue;
                  add_edge(u, v);
                  IDU[v] ++;
              }
              for(u = 1; u <= N; u ++) {
                  if(IDU[u]) continue;
                  for(int i = 0; i < MM; i++) d[u].fa[i] = u;
                  dfs(u, 1, u);
              }
              scanf("%d", &Q);
              while(Q --) {
                  scanf("%d", &M);
                  for(int i = 0; i < M; i++) {
                      scanf("%d", &buf[i]);
                  }
                  sort(buf, buf + M, cmp);
                  ans = 0;
                  for(int i = 0; i < M; i++) {
                      u = buf[i];
                      bool ok = true;
                      for(int j = 0; j < i; j++) {
                          v = buf[j];
                          if(chosen(u, v)) { ok = false; break; }
                      }
                      if(ok) ans += d[u].sz;
                  }
                  printf("%d\n", ans);
              }
          }
          return 0;
      }

      参考代码2

      /**
       * DFS序
       */
      #include <bits/stdc++.h>
      
      using namespace std;
      
      const int MX = 200000 + 5;
      const int MM = 20 + 5;
      
      int N, Q, M;
      struct Edge {
          int v, next;
      } edge[MX];
      int head[MX], tot;
      int dfn[MX], id, R[MX], L[MX], ans;
      bool flag[MX];
      void init() {
          tot = 0;
          id = 0;
          memset(head, -1, sizeof(head));
          memset(flag, false, sizeof(flag));
      }
      
      void add_edge(int u, int v) {
          edge[tot] = Edge{v, head[u]};
          head[u] = tot ++;
      }
      
      void dfs(int u) {
          int v;
          L[u] = dfn[u] = ++ id; /// [L, R]
          for(int i = head[u]; ~i; i = edge[i].next) {
              v = edge[i].v;
              dfs(v);
          }
          R[u] = id;
      }
      struct INode {
          int s, t;
          int get() { return t - s + 1; }
          bool operator < (const INode& e) const {
              if(s == e.s) return t < e.t;
              return s < e.s;
          }
      } I[MM];
      int calc(int M) {
          int ret = 0, cur = 0;
          sort(I, I + M);
          for(int i = 0; i < M; i++) {
              if(cur >= I[i].t) continue;
              if(cur < I[i].s) cur = I[i].t, ret += I[i].get();
              else if(cur < I[i].t) cur = I[i].t, ret += I[i].t - cur;
          }
          return ret;
      }
      int main() {
          int u, v;
          while(~scanf("%d", &N)) {
              init();
              for(v = 1; v <= N; v++) {
                  scanf("%d", &u);
                  if(u == 0) continue;
                  add_edge(u, v);
                  flag[v] = true;
              }
              for(u = 1; u <= N; u++) {
                  if(flag[u]) continue;
                  dfs(u);
              }
              scanf("%d", &Q);
              while(Q --) {
                  scanf("%d", &M);
                  ans = 0;
                  for(int i = 0; i < M; i++) {
                      scanf("%d", &u);
                      I[i] = INode{L[u], R[u]};
                  }
                  ans = calc(M);
                  printf("%d\n", ans);
              }
          }
          return 0;
      }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值