首页 > 其他分享 >换根树形动态规划

换根树形动态规划

时间:2023-12-21 19:15:05浏览次数:26  
标签:size 子树 int 树形 为根 include 换根 号点 动态

换根树形动态规划

考虑以1为根的情况,size[i]表示以i为根的子树中有多少个点,f[i]表示考虑以i为根的子树,i到子树其他所有点的距离的和;

假设j是i的儿子,以j为根的子树对f[i]的贡献为f[j]+size[j]

\[f[i] = \sum_{j\in son(i)}(f[j]+size[j])= \sum_{j\in son(i)}(f[j]) + size[i] -1 \]

当根从1变到他的儿子i的时候呢?

这时除了1为根时考虑的子树(贡献为f[i]),多了一个以1为根的子树,后面贡献怎么求?

用v[i]表示一个点的父亲作为它的子树时的贡献,v[i] = 0;

根从1号点变成其儿子结点:

\[v[i] = v[1] + f[1] - f[i] - size[i] + n - size[i]; \]

\(v[1]\)

这里添加是为了更好的理解普通情况, 根从某个点变为其子节点时子节点继承v[i]

\(f[1] - f[i] - size[i]\)

此时1号点变为子节点减去现在作为根的节点对1号点的贡献

\(n-size[i]\)

当i号点作为根节点时, 此时原来以1号点为根的结点(1号点及原来以i号点为根的子树外的所有结点)都要让距离加一才能到i号点

根从x变为儿子y:

除了1为根时考虑的子树(贡献为f[y]), 多了一个以x为根的子树, 后面这个贡献:

\(v[y] = v[x] + f[x] - f[y] - size[y] + n - size[y];\)

此时答案就变成了子树内的距离f[i]加上v[i], \(f[i]+v[i]\)

时间复杂度\(O(n)\)

#include <algorithm>
#include <array>
#include <cmath>
#include <cstring>
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <vector>
using namespace std;
#define x first
#define y second
typedef pair<int, int> PII;
typedef long long LL;
const int N = 1e5 + 10;
int n, l, siz[N];
vector<int> g[N];
LL f[N], v[N];
bool b[N];
void up(int i) {
  siz[i] = 1;
  b[i] = true;
  for (int j : g[i]) {
    if (!b[j]) {
      up(j);
      siz[i] += siz[j];
      f[i] += f[j];
    }
  }
  f[i] += siz[i] - 1;
}
void down(int i) {
  b[i] = true;
  for (int j : g[i]) {
    if (!b[j]) {
      v[j] = v[i] + f[i] - f[j] - siz[j] + n - siz[j];
      down(j);
    }
  }
}
int main() {
  scanf("%d", &n);
  for (int i = 1; i < n; i++) {
    int x, y;
    scanf("%d%d", &x, &y);
    g[x].push_back(y);
    g[y].push_back(x);
  }
  memset(b, false, sizeof(b));
  up(1);
  memset(b, false, sizeof(b));
  down(1);
  for (int i = 1; i <= n; i++) {
    printf("%lld\n", f[i] + v[i]);
  }

  return 0;
}

image.png

考虑以1为根的有根树的情况, f[i] 表示考虑以i为根的子树, 最多可以承接多少上面下来的流量;

假设j是i的儿子, 以j为根的子树对f[i]的贡献为\(min(c[j], f[j])\),其中c[j]表示j到i的边的流量限制;

\[f[i] = \sum_{j\in son(i)}min(c[j], f[j]) \]

当根从1变为儿子i时:

除了1为根时考虑的子树(贡献为f[i]), 多了一个以1为根的子树, 后面贡献怎么求?

v[i] 表示一个点的父亲作为它的子树时的贡献, v[1] = 0;

\[v[i] = min(v[1] + f[1] - min(c[i], f[i]), c[i]) \]

当根从x变为儿子y时:
除了以1为根时考虑的子树(贡献为f[y]), 多了一个以x为根的子树

\[v[y] = min(v[x] + f[x] -min(c[y], f[y]), c[y]) \]

对于y, 答案\(v[y] + f[y]\)

时间复杂度\(O(n)\)

#include <algorithm>
#include <array>
#include <cmath>
#include <cstring>
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <vector>
using namespace std;
#define x first
#define y second
typedef pair<int, int> PII;
typedef long long LL;
const int N = 1e5 + 10;
int n, l;
vector<PII> g[N];
LL f[N], v[N];
bool b[N];
void up(int i) {
  b[i] = true;
  bool ok = true;
  for (auto j : g[i]) {
    if (!b[j.x]) {
      up(j.x);
      f[i] += min(1LL * j.y, f[j.x]);
      ok = false;
    }
  }
  if (ok) f[i] = 1 << 30;
}
void down(int i) {
  b[i] = true;
  for (auto j : g[i]) {
    if (!b[j.x]) {
      if (v[i] + f[i] - min(1LL * j.y, f[j.x]))
        v[j.x] = min(v[i] + f[i] - min(1LL * j.y, f[j.x]), 1LL * j.y);
      else
        v[j.x] = j.y;
      down(j.x);
    }
  }
}
int main() {
  scanf("%d", &n);
  for (int i = 1; i < n; i++) {
    int x, y, z;
    scanf("%d%d%d,", &x, &y, &z);
    g[x].push_back({y, z});
    g[y].push_back({x, z});
  }
  memset(b, false, sizeof(b));
  up(1);
  memset(b, false, sizeof(b));
  down(1);
  for (int i = 1; i <= n; i++) {
    // printf("%lld\n", f[i] + v[i]);
    if (f[i] != 1 << 30)
      printf("%lld\n", f[i] + v[i]);
    else
      printf("%lld\n", v[i]);
  }

  return 0;
}

image.png

考虑以1为根的有根树, f[i]数组记录考虑以i为跟的子树, 从i的不同子树连上来的到i的最长的两条路径;

假设j是i的儿子, 以j为根的子树连上来的路径是连到j的最长路径加i

维护到i的最长的两条路径是: 到1的最长的两条路径的长度的和

根从1变为儿子i时:

此时除了1为根时考虑的子树(f[i]维护了到i的最长的两条路径), 多了一个以1为根的子树

v[i]表示一个点的父亲作为它的子树时从里面连上来的到i的最长路径

v[i]的维护:
直觉上应该将连到1号点的最长路径加1接上j的最长路径, 但是如果1号点的最长路径就是j提供的此时就同一条路加两次了, 要分辨一下

根从x变为儿子y时:

v[y]的维护:
对于y, 以它为根时的答案等于f[y]维护的两条路劲和v[y]维护的路径中较长的两条

#include <algorithm>
#include <array>
#include <cmath>
#include <cstring>
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <vector>
using namespace std;
#define x first
#define y second
typedef pair<int, int> PII;
typedef long long LL;

const int N = 1e5 + 10;
vector<int> g[N];
int n, f[N][2][2], v[N];
bool b[N];
void up(int i) {
  b[i] = true;
  for (int j : g[i]) {
    if (!b[j]) {
      up(j);
      if (f[j][0][0] + 1 > f[i][0][0])
        f[i][1][0] = f[i][0][0], f[i][1][1] = f[i][0][1],
        f[i][0][0] = f[j][0][0] + 1, f[i][0][1] = j;
      else if (f[j][0][0] + 1 > f[i][1][0])
        f[i][1][0] = f[j][0][0] + 1, f[i][1][1] = j;
    }
  }
}
void down(int i) {
  b[i] = true;
  for (int j : g[i]) {
    if (!b[i]) {
      if (f[i][0][1] == j)
        if (v[i] > f[i][1][0])
          v[j] = v[i] + 1;
        else
          v[j] = f[i][1][0] + 1;
      else
        v[j] = max(f[i][0][0], v[i]) + 1;
      down(j);
    }
  }
}
int main() {
  scanf("%d", &n);
  for (int i = 1; i < n; i++) {
    int x, y;
    scanf("%d%d", &x, &y);
    g[x].push_back(y);
    g[y].push_back(x);
  }
  memset(b, false, sizeof(b));
  up(1);
  memset(b, false, sizeof(b));
  down(1);
  for (int i = 1; i <= n; i++)
    printf("%d\n", f[i][0][0] + f[i][1][0] + v[i] -
                       min(min(f[i][0][0], f[i][1][0]), v[i]));

  return 0;
}

标签:size,子树,int,树形,为根,include,换根,号点,动态
From: https://www.cnblogs.com/viewoverlooking/p/17919901.html

相关文章

  • Flutter AnimatedList 实现动态列表
    import'dart:async';import'package:flutter/material.dart';finalGlobalKey_globalKey=GlobalKey();classMyAnimatedListextendsStatelessWidget{constMyAnimatedList({super.key});@overrideWidgetbuild(BuildContextcont......
  • Excel动态图表有多少种类型,你知道哪些?
    折线图:折线图是最常见的动态图表类型之一,它可以清晰地展现数据随时间变化的趋势。在Excel中,您可以轻松创建动态折线图,使数据的变化更加生动。柱状图:动态柱状图可以清晰地比较不同类别的数据,并随着数据的变化而自动更新。这种图表类型在展示多个数据项之间的对比时非常有用。饼图:......
  • class083 动态规划中用观察优化枚举的技巧-下【算法】
    class083动态规划中用观察优化枚举的技巧-下【算法】算法讲解083【必备】动态规划中用观察优化枚举的技巧-下code11235.规划兼职工作//规划兼职工作//你打算利用空闲时间来做兼职工作赚些零花钱,这里有n份兼职工作//每份工作预计从startTime[i]开始、endTime[i]结束,报酬为pr......
  • class068 更多的动态规划【算法】
    class068更多的动态规划【算法】算法讲解068【必备】见识更多二维动态规划题目code1115.不同的子序列//不同的子序列//给你两个字符串s和t,统计并返回在s的子序列中t出现的个数//测试链接:https://leetcode.cn/problems/distinct-subsequences/dp[i][j]:s[前i个]......
  • class069 从递归入手三维动态规划【算法】
    class069从递归入手三维动态规划code1474.一和零//一和零(多维费用背包)//给你一个二进制字符串数组strs和两个整数m和n//请你找出并返回strs的最大子集的长度//该子集中最多有m个0和n个1//如果x的所有元素也是y的元素,集合x是集合y的子集//......
  • class067 二维动态规划【算法】
    class067二维动态规划code164.最小路径和//最小路径和//给定一个包含非负整数的mxn网格grid//请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。//说明:每次只能向下或者向右移动一步。//测试链接:https://leetcode.cn/problems/minimum-path-sum/d......
  • class066 一维动态规划【算法】
    class066一维动态规划算法讲解066【必备】从递归入手一维动态规划code1509斐波那契数列//斐波那契数//斐波那契数(通常用F(n)表示)形成的序列称为斐波那契数列//该数列由0和1开始,后面的每一项数字都是前面两项数字的和。//也就是:F(0)=0,F(1)=1//F(n)=F(n-1......
  • vue3+vite动态引入图片(import.meta.glob)
    Vite官方提供的 import.meta.glob API。这个方法一般用于批量引入js或者ts文件,但实际上这个方法就是很多import语句的集合而已,import是可以引入图片的,所以import.meta.glob也同样可以引入图片资源,只不过需要加入配置项as:'url'就可以了。 通常来说,我们可以用ES提供的......
  • Vue中动态(import 、require)显示img图片
    vue中,经常会遇到显示图片的问题,如果是一个普通组件的话,那么这样就可以了<imgsrc="../assets/images/avtor.jpg"width="100%">上文的弊端有两个:首先,是采用绝对路径引入。如果以后图片移动了位置,需要修改代码。其次,如果说图片是一个logo图片,同一页面内有多处用到。就需要引用......
  • 算法分析-动态规划-求解0-1背包问题
    一.题目需求 使用一个体积大小为13的背包,选择一件或多件商品带走,使得所选商品总价值最大。商品列表如下: 二.算法思想1,这是一个经典的0-1背包问题它要求我们在一组物品中选择一些,每个物品只能选择一次或者不选择,目标是使得所选物品的总价值最大。这个问题在实际生活中有很......