P4175 [CTSC2008] 网络管理 题解

前置知识:P2633 Count on a tree & P2617 Dynamic Rankings 。如果对本题一点思路都没有的话建议先做以上两题。


首先考虑如何在序列上实现本题操作,即单点修改区间 kth。如果不考虑修改的话可以直接用可持久化线段树做。那么现在的问题就是如何实现修改操作。如果暴力地做,则要对后缀中每个线段树进行修改,必 T。所以要对此进行优化。

回想一下 【模板】可持久化线段树 2。这道题我们在查询 [l,r] 时并不单独在某棵线段树上面跑,而是将 rl1 的两棵线段树相减。把每棵线段树当做一个记录从 1 到 i 这个前缀每个数字出现次数的集合,记作 precnti。把 [l,r] 每个数字出现次数的集合记作 cnt(l,r),则 cnt(l,r)=precntrprecntl1

可以发现这种形式就是查询前缀和再差分得到答案。由于树状数组在单点修改和查询前缀和上有着优秀的复杂度和常数,所以我们可以把线段树套在 BIT 里面。具体地,即每个 BIT 节点维护一个互不相交的(动态开点)线段树的根。

这种数据结构每次修改时使用类似 BIT 的方式,在 log 棵线段树上更新。查询时同样类似树状数组,首先记下 l1r 各自对应的 log 棵线段树,然后计算时用所有 r 对应的线段树减去所有 l1 对应的线段树。

时间复杂度 O(nlog2n)

核心代码如下。(为 P2617 Dynamic Rankings 代码片段。)

// 这个代码现在看来太屎山了。但我懒得重构了,能过就行。
int query(vector<int> &r1, vector<int> &r2, int k, int left, int right) {
  int mid = (left + right) >> 1;
  if (left == right)
    return left;
  int left_val = 0;
  for (int it : r1)  //用所有 r 对应的线段树的当前节点的左儿子减去 l-1 对应的
    left_val -= (segTree[it].lson != -1 ? segTree[segTree[it].lson].val : 0);
  for (int it : r2)
    left_val += (segTree[it].lson != -1 ? segTree[segTree[it].lson].val : 0);
  if (left_val >= k) {
    for (int &it : r1) {
      if (segTree[it].lson == -1) {
        int new_id       = new_node();
        segTree[it].lson = new_id;
      }
      it = segTree[it].lson;
    }
    for (int &it : r2) {
      if (segTree[it].lson == -1) {
        int new_id       = new_node();
        segTree[it].lson = new_id;
      }
      it = segTree[it].lson;
    }
    return query(r1, r2, k, left, mid);
  } else {
    for (int &it : r1) {
      if (segTree[it].rson == -1) {
        int new_id       = new_node();
        segTree[it].rson = new_id;
      }
      it = segTree[it].rson;
    }
    for (int &it : r2) {
      if (segTree[it].rson == -1) {
        int new_id       = new_node();
        segTree[it].rson = new_id;
      }
      it = segTree[it].rson;
    }
    return query(r1, r2, k - left_val, mid + 1, right);
  }
}

void update(int r, int id, int up, int left, int right) {
  if (left == id && right == id) {
    segTree[r].val += up;
    return;
  }
  int mid = (left + right) >> 1;
  if (id <= mid) {
    if (segTree[r].lson == -1) {
      int new_id = new_node();
      segTree[r].lson = new_id;
    }
    update(segTree[r].lson, id, up, left, mid);
  } else {
    if (segTree[r].rson == -1) {
      int new_id      = new_node();
      segTree[r].rson = new_id;
    }
    update(segTree[r].rson, id, up, mid + 1, right);
  }
  segTree[r].val = (segTree[r].lson != -1 ? segTree[segTree[r].lson].val : 0) +
                   (segTree[r].rson != -1 ? segTree[segTree[r].rson].val : 0);
}

inline int QUERY(int l, int r, int k) {
  vector<int> r1, r2;  //存 l-1 和 r 各自对应的线段树。
  for (int i = l - 1; i; i -= lowbit(i))
    if (root[i] != -1)
      r1.push_back(root[i]);
  for (int i = r; i; i -= lowbit(i))
    if (root[i] != -1)
      r2.push_back(root[i]);
  return query(r1, r2, k, 1, kind);
}

inline void UPDATE(int id, int num, int up, int n) {
  for (int i = id; i <= n; i += lowbit(i)) {
    if (root[i] == -1)
      root[i] = new_node();
    update(root[i], num, up, 1, kind);
  }
}

现在只需要把上面的东西搬到树上就行了。实际上这个和在序列上是本质相同的。树上差分即可。设 chain_sum(a,b) 为从 ab 的权值和,sumi 为从根节点到 i 的权值和。由于树上差分的公式 chain_sum(a,b)=suma+sumbsumlcasumfalca。查询时用所有 ab 对应的线段树减去 lcafalca 对应的线段树。

注意修改时因为修改某个节点只会影响到自身及其子树,所以要消除对无关节点的影响。
为此需要求出每个节点的 DFS 序作为树状数组中的下标,使得每个节点及其子树中节点的下标在树状数组上是连续的,便于差分来消除对无关节点的影响。因为还要求 LCA,所以可用树链剖分实现这两个需求。

时间复杂度同上,为 O(nlog2n)

Code:

const int maxn = 80010;
int a[maxn], MAP[maxn << 1], kind;
struct OPERATION {
  int a, b, k;
} op[maxn];  //记录操作
vector<int> edge[maxn];
int fa[maxn], dep[maxn], Size[maxn], son[maxn];
int id[maxn], top[maxn], Index;

void dfs1(int u, int FA) {  //树剖。
  fa[u]   = FA;
  dep[u]  = dep[FA] + 1;
  Size[u] = 1;
  son[u]  = 0;
  for (int v : edge[u]) {
    if (v == FA)
      continue;
    dfs1(v, u);
    Size[u] += Size[v];
    if (Size[v] > Size[son[u]])
      son[u] = v;
  }
}
void dfs2(int u, int TOP) {
  id[u]  = ++Index;
  top[u] = TOP;
  if (!son[u])
    return;
  dfs2(son[u], TOP);
  for (int v : edge[u]) {
    if (v == fa[u] || v == son[u])
      continue;
    dfs2(v, v);
  }
}
inline int get_LCA(int a, int b) {
  while (top[a] != top[b]) {
    if (dep[top[a]] < dep[top[b]])
      swap(a, b);
    a = fa[top[a]];
  }
  return dep[a] < dep[b] ? a : b;
}
void disc(int MAP[], int n, int q, int len, int &m) {  //离散化。
  sort(MAP + 1, MAP + len + 1);
  m = unique(MAP + 1, MAP + len + 1) - (MAP + 1);
  for (int i = 1; i <= n; i++)
    a[i] = lower_bound(MAP + 1, MAP + m + 1, a[i]) - MAP;
  for (int i = 1; i <= q; i++)
    if (!op[i].k)
      op[i].b = lower_bound(MAP + 1, MAP + m + 1, op[i].b) - MAP;
}

inline int lowbit(int x) {
  return x & (-x);
}
struct segTreeType {
  struct segTreeNode {
    int val;
    int lson, rson;
    segTreeNode() {
      lson = rson = -1, val = 0;
    }
  };
  vector<segTreeNode> segTree;
  int root[maxn];
  segTreeType() {
    memset(root, -1, sizeof(root));
  }
  inline int new_node() {
    segTree.push_back(segTreeNode());
    return segTree.size() - 1;
  }
  void change_to_lson(vector<int> &r) {
    for (int &it : r) {
      if (segTree[it].lson == -1) {
        int new_id       = new_node();
        segTree[it].lson = new_id;
      }
      it = segTree[it].lson;
    }
  }
  template <class T, class... Args>
  void change_to_lson(T &head, Args &...args) {
    change_to_lson(head), change_to_lson(args...);
  }
  void change_to_rson(vector<int> &r) {
    for (int &it : r) {
      if (segTree[it].rson == -1) {
        int new_id       = new_node();
        segTree[it].rson = new_id;
      }
      it = segTree[it].rson;
    }
  }
  template <class T, class... Args>
  void change_to_rson(T &head, Args &...args) {
    change_to_rson(head), change_to_rson(args...);
  }

  int query(vector<int> &r1, vector<int> &r2, vector<int> &r3, vector<int> &r4, int k, int left, int right) {
    int mid = (left + right) >> 1;
    if (left == right) {
      int val = 0;  //要判断个数能否满足要求(>=k),<k 则无解。
      for (int it : r1)
        val += segTree[it].val;
      for (int it : r2)
        val += segTree[it].val;
      for (int it : r3)
        val -= segTree[it].val;
      for (int it : r4)
        val -= segTree[it].val;
      if (k <= val)
        return left;
      return -1;
    }
    int right_val = 0;
    for (int it : r1)  //因为本题求第 k “大”值所以要把前一份代码中的左儿子改成右儿子。
      right_val += (segTree[it].rson != -1 ? segTree[segTree[it].rson].val : 0);
    for (int it : r2)
      right_val += (segTree[it].rson != -1 ? segTree[segTree[it].rson].val : 0);
    for (int it : r3)
      right_val -= (segTree[it].rson != -1 ? segTree[segTree[it].rson].val : 0);
    for (int it : r4)
      right_val -= (segTree[it].rson != -1 ? segTree[segTree[it].rson].val : 0);
    if (right_val >= k) {
      change_to_rson(r1, r2, r3, r4);
      return query(r1, r2, r3, r4, k, mid + 1, right);
    } else {
      change_to_lson(r1, r2, r3, r4);
      return query(r1, r2, r3, r4, k - right_val, left, mid);
    }
  }
  void update(int r, int id, int up, int left, int right) {
    if (left == id && right == id) {
      segTree[r].val += up;
      return;
    }
    int mid = (left + right) >> 1;
    if (id <= mid) {
      if (segTree[r].lson == -1) {
        int new_id      = new_node();
        segTree[r].lson = new_id;
      }
      update(segTree[r].lson, id, up, left, mid);
    } else {
      if (segTree[r].rson == -1) {
        int new_id      = new_node();
        segTree[r].rson = new_id;
      }
      update(segTree[r].rson, id, up, mid + 1, right);
    }
    segTree[r].val = (segTree[r].lson != -1 ? segTree[segTree[r].lson].val : 0) +
                     (segTree[r].rson != -1 ? segTree[segTree[r].rson].val : 0);
  }

  inline int QUERY(int a, int b, int k) {  //要注意用 DFS 序作为下标而不是原本编号。
    vector<int> r1, r2, r3, r4;
    int lca = get_LCA(a, b), lcafa = fa[lca];
    for (int i = id[a]; i; i -= lowbit(i))
      if (root[i] != -1)
        r1.push_back(root[i]);
    for (int i = id[b]; i; i -= lowbit(i))
      if (root[i] != -1)
        r2.push_back(root[i]);
    for (int i = id[lca]; i; i -= lowbit(i))
      if (root[i] != -1)
        r3.push_back(root[i]);
    for (int i = id[lcafa]; i; i -= lowbit(i))
      if (root[i] != -1)
        r4.push_back(root[i]);
    return query(r1, r2, r3, r4, k, 1, kind);
  }

  inline void UPDATE(int id, int num, int up, int n) {
    for (int i = id; i <= n; i += lowbit(i)) {
      if (root[i] == -1)
        root[i] = new_node();
      update(root[i], num, up, 1, kind);
    }
  }
} segTree;

int main() {
  int n, q, len = 0;
  in(n, q);
  for (int i = 1; i <= n; i++)
    in(a[i]), MAP[++len] = a[i];
  for (int i = 1; i < n; i++) {
    int a, b;
    in(a, b);
    edge[a].push_back(b);
    edge[b].push_back(a);
  }
  for (int i = 1; i <= q; i++) {
    in(op[i].k, op[i].a, op[i].b);
    if (!op[i].k)
      MAP[++len] = op[i].b;
  }
  disc(MAP, n, q, len, kind);
  dfs1(1, 0);
  dfs2(1, 1);

  for (int i = 1; i <= n; i++)
    segTree.UPDATE(id[i], a[i], 1, n), segTree.UPDATE(id[i] + Size[i], a[i], -1, n);  //要消除对无关节点影响。
  for (int i = 1; i <= q; i++) {
    if (op[i].k) {
      int res = segTree.QUERY(op[i].a, op[i].b, op[i].k);
      if (res != -1)
        out(MAP[res]), enter;
      else
        puts("invalid request!");
    } else {
      segTree.UPDATE(id[op[i].a], a[op[i].a], -1, n);
      segTree.UPDATE(id[op[i].a] + Size[op[i].a], a[op[i].a], 1, n);
      a[op[i].a] = op[i].b;
      segTree.UPDATE(id[op[i].a], a[op[i].a], 1, n);
      segTree.UPDATE(id[op[i].a] + Size[op[i].a], a[op[i].a], -1, n);
    }
  }
}

UPD:修改部分描述使其更加严谨。

posted @   MeteorFlower  阅读(38)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示