题目链接:


题目大意:

给定一棵树,有n个点,每个点(假设点为v)给定两个值分别是lv和rv,再给定n-1条边,对于每条边来说它的贡献值是|ai - aj|,i 和 j 是边的两个点,ai 是介于 li 和 ri 之间的任意一个值,aj 同理。 求这个树最大的贡献值是多少。

解题思路:

第一时间的想法就是每个点必须选取极端的值,因为对于任意边来说,两点差的绝对值最大肯定是极端值的情况。
Test case 34,6讨论一下:

  1. 很明显4,6的最大贡献值要选取2和17
  2. 如果不这么选而选取中间值,那就只有一个理由:2和3的那条边影响到了4
  3. 但是下面那条边如果想要选取出最大贡献,也要取极端值
  4. 而如果6号点选取了中间值,那很明显对于4和6的贡献都不是最大的
  5. 什么,2和4也不选取极端值,那你可以选择暴力枚举出来,6、2、3他们的贡献值一定没刚才大

cf:Parsa's Humongous Tree - 图1

当然,上面只是为了说明选取极端值才是最好的,并不是最重要的东西。
只有选取极端值我们才可以用dp来做。(不然枚举的第二维太多了,1e9存都存不下)

还以Test case 3的6、4、2、3举例,他们的最大贡献无非就是左右枚举一遍,即(3,2,12,12)、(3,12,20,19)。。。。。
那么爆搜一遍答案其实就出来了,并且是正确的,如下:

TLE代码:

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. const int N = 1e5 + 50;
  4. typedef long long LL;
  5. int e[N], ne[N], h[N], idx;
  6. void add(int a, int b)
  7. {
  8. e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
  9. }
  10. int l[N], r[N];
  11. int t, n;
  12. int f[N];
  13. int dfs(int u, int cs, int f) {
  14. int dist = 0;
  15. for (int i = h[u]; ~i; i = ne[i]) {
  16. int j = e[i];
  17. if (j == f) continue; // 防止死循环
  18. // 就是这里,选取相邻结点的两个极端值即可
  19. dist += max(dfs(j, l[j], u) + abs(cs - l[j]), dfs(j, r[j], u) + abs(cs - r[j]));
  20. }
  21. return dist;
  22. }
  23. int main()
  24. {
  25. cin >> t;
  26. while (t -- ) {
  27. memset(h, -1, sizeof h);
  28. scanf("%d", &n);
  29. for (int i = 1; i <= n; i ++ ) scanf("%d%d", &l[i], &r[i]);
  30. for (int i = 0; i < n - 1; i ++ ) {
  31. int a, b;
  32. scanf("%d%d", &a, &b);
  33. if (a > b) swap(a, b);
  34. add(a, b);
  35. add(b, a);
  36. }
  37. printf("%d\n", max(dfs(1, l[1], -1), dfs(1, r[1], -1)));
  38. }
  39. return 0;
  40. }

但是很容易发现,这个复杂度是指数级别的,所以我很快啊,啪的一下就TLE了。。 仔细看其实这个代码是从上往下找的,即要往左找一遍,往右找一遍,这才导致复杂度高。 如果先处理出来子节点的左右max值,当前节点直接用,就变成了线性 可以这么做的原因是并不像传统的二叉树有两个儿子,这题只有一个儿子,但是有两个值而已

AC代码:

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. typedef long long LL;
  4. const int N = 1e6 + 20;
  5. int n;
  6. int e[N], ne[N], h[N], idx;
  7. LL l[N], r[N];
  8. void add(int a, int b) {
  9. e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
  10. }
  11. int t;
  12. LL dp[N][2];
  13. void dfs(int u, int f) {
  14. dp[u][0] = dp[u][1] = 0;
  15. for (int i = h[u]; ~i ; i = ne[i]) {
  16. int j = e[i];
  17. if (j == f) continue;
  18. // 这里让他先递归下去,记录下以j为根的最优解
  19. dfs(j, u);
  20. // 这里方程没变,直接用就可以了
  21. // 此时递归两次变成了递归一次,但是一次就记录了最优解
  22. dp[u][0] += max(dp[j][0] + abs(l[u] - l[j]), dp[j][1] + abs(l[u] - r[j]));
  23. dp[u][1] += max(dp[j][0] + abs(r[u] - l[j]), dp[j][1] + abs(r[u] - r[j]));
  24. }
  25. }
  26. int main()
  27. {
  28. cin >> t;
  29. while (t -- ) {
  30. memset(h, -1, sizeof h);
  31. scanf("%d", &n);
  32. for (int i = 1; i <= n; i ++ ) scanf("%d%d", &l[i], &r[i]);
  33. for (int i = 0; i < n - 1; i ++ ) {
  34. int a, b;
  35. scanf("%d%d", &a, &b);
  36. add(a, b);
  37. add(b, a);
  38. }
  39. dfs(1, -1);
  40. printf("%lld\n", max(dp[1][0], dp[1][1]));
  41. }
  42. return 0;
  43. }