一棵有 N 个节点的树,节点编号为 1..N。每个节点都有一个权值。有 4 种类型的操作:
① 1 x y a b ,从树中删除一条边 x -y ,然后添加一条新边 a -b ;确保在添加新边后它仍然构成树。
② 2 a b w ,将节点 a 和 b 路径上所有节点(包括a 和b)的权值都修改为 w。
③ 3 a b d ,将节点 a 和 b(包括 a 和 b )路径上所有节点的权值都增加 d。
④ 4 a b ,查询节点 a 和 b (包括 a 和 b )路径上的第 2 大权值,以及该权值在路径上出现的次数。
注意:这里是严格的第 2 大权值。{3, 5, 2, 5, 3} 的严格第 2 大权值是 3。
第 1 行包含一个整数 T(T ≤ 3),表示测试用例数。每个测试用例的第 1 行都包含两个整数 N 和 M( N、M ≤10^5 ),表示节点数和操作数;第2行包含 N 个整数,表示节点的权值;接下来的 N-1 行,每行都包含两个整数 a 和 b ,表示在节点 a 和 b 之间有一条边;最后 M 行描述操作。其中,1≤x , y , a , b ≤N ,|w |≤10^4 ,|d|≤10^4 。
对每个测试用例,都先单行输出“Case #x :”(x 表示测试用例编号);对每个查询操作都输出两个值,即第 2 大权值及其出现的次数。若路径上所有节点的权值都相同,则输出“ALL SAME”。
2
3 2
1 1 2
1 2
1 3
4 1 2
4 2 3
7 7
5 3 2 1 7 3 6
1 2
1 3
3 4
3 5
4 6
4 7
4 2 6
3 4 5 -1
4 5 7
1 3 4 2 4
4 3 6
2 3 6 5
4 3 6
Case #1:
ALL SAME
1 2
Case #2:
3 2
1 1
3 2
ALL SAME
根据输入样例 2,构建的树形结构如下图所示。

它对应的 7 种操作如下。
(1)4 2 6:查询 2-6 路径上的第 2 大权值和该权值的重复次数。2-6 路径上的第 2 大权值为 3,该权值的重复次数为 2,所以输出 3 2。

(2)3 4 5 -1:将 4-5 路径上所有节点的权值都减 1。

(3)4 5 7:查询 5-7 路径上的第 2 大权值和该权值的重复次数,路径 5-7 上的第 2 大权值为 1,该权值的重复次数为 1,输出 1 1。

(4)1 3 4 2 4:删除 3-4 的边,添加 2-4 的边。

(5)4 3 6:查询 3-6 路径上的第 2 大权值和该权值的重复次数,路径 3-6 上的第 2 大权值为 3,该权值的重复次数为 2,输出 3 2。
(6)2 3 6 5:将 3-6 路径上所有节点的权值都修改为 5。

(7)4 3 6:查询 3-6 路径上的第 2 大权值和该权值的重复次数,路径 3-6 上的所有权值都相同,输出“ALL SAME”。
本问题为动态树问题,包括删边/连边、区间更新、区间修改、区间第 2 大值查询。
删除一条边 x -y ,然后添加一条新边 a -b 。只需执行 cut(x , y)、link(a , b)即可。
将 a -b 路径上所有节点的权值都修改为 x 。首先执行 split(a , b),切分出 a -b 路径,然后执行 change(b , x ),将以 b 为根的子树上的所有节点都修改为x(打懒标记)。
将 a -b 路径上所有节点的权值都增加 d 。首先执行 split(a , b),切分出 a -b 路径,然后执行 addval(b , d),将以 b 为根的子树上的所有节点都增加d(打懒标记)。
查询 a -b 路径上所有节点的第 2 大权值及该权值出现的次数。首先执行split(a , b),切分出 a-b 路径,然后执行 query(a , b),若第2大值的重复次数等于以 y 为根的子树大小,则输出“ALL SAME”,否则输出第 2 大值及其重复次数。
- package com.platform.modules.alg.alglib.hdu5002;
-
- /**
- * @className: Hdu5002
- * @description: 参考 http://hzwer.com/5540.html
- * @date: 2022/11/17
- * @author: chengqiuming
- */
- public class Hdu5002 {
- private int inf = 0x3f3f3f3f;
- private int N = 100005;
-
- int n, m, top;
- int c[][] = new int[N][2];
- int fa[] = new int[N];
- int v[] = new int[N];
- int st[] = new int[N];
- int mx1[] = new int[N]; // 最大值
- int mx2[] = new int[N]; // 次大值
- int c1[] = new int[N]; // 两者的重复次数
- int c2[] = new int[N]; // 子树大小
- int size[] = new int[N]; // 子树大小
- int ta[] = new int[N]; // 增加懒标记
- int tc[] = new int[N]; // 修改懒标记
- int rev[] = new int[N]; // 反转懒标记
-
- public String output = "";
-
- // 统计 x 的子树中最大、次大值和重复次数,当前节点值为 val,其重复次数为 c
- void cnt(int x, int val, int c) {
- if (val > mx1[x]) {
- mx2[x] = mx1[x];
- mx1[x] = val;
- c2[x] = c1[x];
- c1[x] = c;
- } else if (val == mx1[x]) {
- c1[x] += c;
- } else if (val > mx2[x]) {
- mx2[x] = val;
- c2[x] = c;
- } else if (val == mx2[x]) {
- c2[x] += c;
- }
- }
-
- // y 为根的子树上所有节点改为 val
- void change(int y, int val) {
- mx1[y] = val;
- v[y] = val;
- c1[y] = size[y];
- mx2[y] = -inf;
- c2[y] = 0;
- tc[y] = val;
- if (ta[y] != 0) ta[y] = 0;
- }
-
- // y 为根的子树上所有节点加 val
- void addval(int y, int val) {
- ta[y] += val;
- mx1[y] += val;
- v[y] += val;
- if (mx2[y] != -inf) mx2[y] += val;
- }
-
- void update(int x) {
- int l = c[x][0], r = c[x][1];
- mx1[x] = mx2[x] = -inf;
- c1[x] = c2[x] = 0;
- cnt(x, v[x], 1);
- if (l > 0) {
- cnt(x, mx1[l], c1[l]);
- cnt(x, mx2[l], c2[l]);
- }
- if (r > 0) {
- cnt(x, mx1[r], c1[r]);
- cnt(x, mx2[r], c2[r]);
- }
- size[x] = size[l] + size[r] + 1;
- }
-
- void pushdown(int x) {
- int l = c[x][0], r = c[x][1];
- if (rev[x] == 1) {//反转
- rev[x] ^= 1;
- rev[l] ^= 1;
- rev[r] ^= 1;
- // 交换两者的值,不要写l,r
- int temp = c[x][0];
- c[x][0] = c[x][1];
- c[x][1] = temp;
- }
- if (tc[x] != -inf) {//修改
- if (l > 0) change(l, tc[x]);
- if (r > 0) change(r, tc[x]);
- tc[x] = -inf;
- }
- if (ta[x] != 0) {//增加
- if (l > 0) addval(l, ta[x]);
- if (r > 0) addval(r, ta[x]);
- ta[x] = 0;
- }
- }
-
- boolean isroot(int x) {
- return c[fa[x]][0] != x && c[fa[x]][1] != x;
- }
-
- void rotate(int x) {
- int y = fa[x], z = fa[y], l, r;
- if (c[y][0] == x) l = 0;
- else l = 1;
- r = l ^ 1;
- if (!isroot(y)) {
- if (c[z][0] == y) c[z][0] = x;
- else c[z][1] = x;
- }
- fa[x] = z;
- fa[y] = x;
- fa[c[x][r]] = y;
- c[y][l] = c[x][r];
- c[x][r] = y;
- update(y);
- update(x);
- }
-
- void splay(int x) {
- top = 0;
- st[++top] = x;
- for (int i = x; !isroot(i); i = fa[i])
- st[++top] = fa[i];
- while (top > 0) pushdown(st[top--]);
- while (!isroot(x)) {
- int y = fa[x], z = fa[y];
- if (!isroot(y)) {
- if (c[y][0] == x ^ c[z][0] == y) {
- rotate(x);
- } else {
- rotate(y);
- }
- }
- rotate(x);
- }
- }
-
- void access(int x) {
- for (int t = 0; x > 0; t = x, x = fa[x]) {
- splay(x);
- c[x][1] = t;
- update(x);
- }
- }
-
- void makeroot(int x) {
- access(x);
- splay(x);
- rev[x] ^= 1;
- }
-
- void link(int x, int y) {
- makeroot(x);
- fa[x] = y;
- }
-
- void split(int x, int y) {
- makeroot(x);
- access(y);
- splay(y);
- }
-
- void cut(int x, int y) {
- split(x, y);
- c[y][0] = fa[c[y][0]] = 0;
- update(y);
- }
-
- int findroot(int x) {
- access(x);
- splay(x);
- while (c[x][0] > 0) x = c[x][0];
- return x;
- }
-
- void query(int x, int y) {
- if (c1[y] == size[y]) {
- output += "ALL SAME";
- } else {
- output += mx2[y] + " " + c2[y] + "\n";
- }
- }
-
- public String cal(String input) {
- int opt, x, y, a, b, d;
- String[] line = input.split("\n");
-
- String[] num = line[0].split(" ");
- n = Integer.parseInt(num[0]);
- m = Integer.parseInt(num[1]);
- String[] wage = line[1].split(" ");
- for (int i = 1; i <= n; i++) {
- v[i] = Integer.parseInt(wage[i - 1]);
- }
-
- for (int i = 1; i <= n; i++) {
- mx1[i] = v[i];
- c1[i] = 1;
- mx2[i] = -inf;
- c2[i] = 0;
- size[i] = 1;
- }
- for (int i = 1; i <= n; i++) {
- fa[i] = c[i][0] = c[i][1] = 0;
- ta[i] = rev[i] = 0;
- tc[i] = -inf;
- }
- for (int i = 1; i < n; i++) {
- String[] edge = line[i + 1].split(" ");
- x = Integer.parseInt(edge[0]);
- y = Integer.parseInt(edge[1]);
- link(x, y);
- }
- int count = 1;
- while (m-- > 0) {
- String[] query = line[n + count++].split(" ");
- opt = Integer.parseInt(query[0]);
- if (opt == 1) {
- x = Integer.parseInt(query[1]);
- y = Integer.parseInt(query[2]);
- a = Integer.parseInt(query[3]);
- b = Integer.parseInt(query[4]);
- cut(x, y);
- link(a, b);
- } else if (opt == 2) {
- a = Integer.parseInt(query[1]);
- b = Integer.parseInt(query[2]);
- x = Integer.parseInt(query[3]);
- split(a, b);
- change(b, x);
- } else if (opt == 3) {
- a = Integer.parseInt(query[1]);
- b = Integer.parseInt(query[2]);
- d = Integer.parseInt(query[3]);
- split(a, b);
- addval(b, d);
- } else {
- a = Integer.parseInt(query[1]);
- b = Integer.parseInt(query[2]);
- split(a, b);
- query(a, b);
- }
- }
-
- return output;
- }
- }
7 7
5 3 2 1 7 3 6
1 2
1 3
3 4
3 5
4 6
4 7
4 2 6
3 4 5 -1
4 5 7
1 3 4 2 4
4 3 6
2 3 6 5
4 3 6
