首页 > 其他分享 >P10974 换根 dp 解题报告

P10974 换根 dp 解题报告

时间:2024-11-27 19:47:03浏览次数:9  
标签:int 源点 流量 节点 P10974 include 换根 dp

题目传送门

题目大意:

给定一颗无根树,有一个节点是源点,度数为 \(1\) 的点是汇点,树上的边有最大流量。除源点和汇点外,其它点不储存水,即流入该点的水量之和等于从该点流出的水量之和。整个水系的流量定义为原点单位时间内能发出的水量

现在需要求出:在流量不超过最大流量的前提下,选取哪个点作为源点,整个水系的流量最大,输出最大值。

思路:

朴素的想法是枚举某个点作为源点,然后做树形 dp,设 \(f_u\) 表示从点 \(u\) 往下流向子树的最大流量,不难得出状态转移方程:

\[f_u = \sum\limits_{j\in Son(u)}\begin{cases}\min(w_i, f_j) & \deg_j > 1\\w_i & \deg_j = 1\end{cases} \]

对于每个点都这样做一遍,取 \(\max\),时间复杂度 \(O(n^2)\)。

暴力 \(\texttt{Code:}\)

#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 200010, M = 400010;
typedef long long ll;
int T, n;
int h[N], e[M], ne[M], w[M], idx;
int deg[N];
ll f[N];

void init() {
    for(int i = 1; i <= n; i++)
        deg[i] = 0, h[i] = -1;
}

inline void add(int a, int b, int c) {
    e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}

void dfs(int u, int fa) {
    f[u] = 0;
    for(int i = h[u]; ~i; i = ne[i]) {
        int j = e[i];
        if(j == fa) continue;
        dfs(j, u);
        if(deg[j] == 1) f[u] += w[i];
        f[u] += min(f[j], (ll)w[i]);
    }
}

void solve() {
    scanf("%d", &n);
    init();
    for(int a, b, c, i = 1; i < n; i++) {
        scanf("%d%d%d", &a, &b, &c);
        add(a, b, c), add(b, a, c);
        deg[a]++, deg[b]++;
    }
    ll ans = 0;
    for(int i = 1; i <= n; i++) dfs(i, -1), ans = max(ans, f[i]);
    printf("%lld\n", ans);
}

int main() {
    scanf("%d", &T);
    while(T--) {
        solve();
    }
    return 0;
}

因为这个题是一个无根树,而我们又要枚举根节点,所以不难想到用换根 dp 来代替源点的枚举。

所以考虑用换根 dp 来优化。

来回顾一下换根 dp 的基本思路:

  1. 第一次 dfs,任选一个点为根进行方才的树形 dp;
  2. 第二次 dfs,从相同的根出发,再扫描一遍从父节点向子结点更新信息,这里多半会用到剔除贡献的问题,要么记最大/次大值和具体从哪个点更新(这个主要用于最大/最小的不满足可减性的信息),要么从第一遍 dfs 的信息更新处倒推(这个一般用于满足可减性的信息)。

对应到这个题上就是思考子节点流向父节点的信息怎么计算。

先画个图:

这里 \(g_u\) 表示 \(u\) 为源点的最大流量。

这道题的信息显然具有可减性。

如图,我们可以考虑先去掉 \(j\) 子树对 \(g_u\) 的贡献得到以 \(u\) 为源点的其他贡献,然后这一部分实际就是从 \(j\) 往上流的最大流量,直接和 \(f_j\) 相加就得到了 \(g_j\)。

但是这里有个魔鬼细节,需要考虑节点的度数,因为度数为 \(1\) 的点在第一遍 dfs 时是直接加上的 \(w_i\),所以在去掉贡献的时候需要判一下。父节点也基本同理。

那么这道题就结束了,时间复杂度 \(O(n)\)。

\(\texttt{AC Code:}\)

#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 200010, M = 400010;
typedef long long ll;
int T, n;
int h[N], e[M], ne[M], w[M], idx;
int deg[N];
ll f[N], g[N];

void init() {
    for(int i = 1; i <= n; i++)
        deg[i] = 0, h[i] = -1;
}

inline void add(int a, int b, int c) {
    e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}

void dfs(int u, int fa) {
    f[u] = 0;
    for(int i = h[u]; ~i; i = ne[i]) {
        int j = e[i];
        if(j == fa) continue;
        dfs(j, u);
        if(deg[j] == 1) f[u] += w[i];
        else f[u] += min(f[j], (ll)w[i]);
    }
}

void dfs2(int u, int fa) {
    for(int i = h[u]; ~i; i = ne[i]) {
        int j = e[i];
        if(j == fa) continue;
        g[j] = f[j];
        if(deg[u] == 1) g[j] += w[i];
        else if(deg[j] == 1) g[j] += min((ll)w[i], g[u] - (ll)w[i]);
        else g[j] += min((ll)w[i], g[u] - min((ll)w[i], f[j]));
        dfs2(j, u);
    }
}

void solve() {
    scanf("%d", &n);
    init();
    for(int a, b, c, i = 1; i < n; i++) {
        scanf("%d%d%d", &a, &b, &c);
        add(a, b, c), add(b, a, c);
        deg[a]++, deg[b]++;
    }
    dfs(1, -1);
    g[1] = f[1];
    dfs2(1, -1);
    ll ans = 0;
    for(int i = 1; i <= n; i++)
        ans = max(ans, g[i]);
    printf("%lld\n", ans);
}

int main() {
    scanf("%d", &T);
    while(T--) {
        solve();
    }
    return 0;
}

标签:int,源点,流量,节点,P10974,include,换根,dp
From: https://www.cnblogs.com/Brilliant11001/p/18572953

相关文章

  • DP 套 DP 与 游园会
    DP套DP听名字猜不到它是个什么东西。接下来用一道例题P459TJOI2018游园会来解释DP套DP。游园会参考资料。题目描述小豆参加了NOI的游园会,会场上每完成一个项目就会获得一个奖章,奖章只会是\(\texttt{N}\)、\(\texttt{O}\)、\(\texttt{I}\)的字样。在会场上他......
  • NOIP 冲刺之——dp
    \(\texttt{0x00}\)前言本篇文章主要记录笔者NOIP冲刺阶段复习的各种dp题型及tricksanstips,同时也用于及时复习与巩固。那么,开始吧。\(\texttt{0x01}\)线性dp线性dp对我来说是一类很捉摸不定的题型:她太综合了,可以和任何知识点合起来考,这里就先抛开“数据结构优化”......
  • 动态规划 区间dp 基础题
    题目19182石子合并(基础版)时间限制:1000MS代码长度限制:10KB提交次数:0通过次数:0题型:编程题语言:不限定Description设有N(N≤300)堆石子排成一排,其编号为1,2,3,⋯,N。每堆石子有一定的质量mi(mi≤1000)。现在要将这N堆石子合并成为一堆。每次只能合并相邻的两堆,......
  • jmeter测试udp接口详解
    jmeter测试udp广播(jmeter发送udp)jmeter测试udp广播(jmeter接收udp)先下载安装第三方插件下载链接:https://jmeter-plugins.org/install/Install/ 将下载的插件放在lib/ext目录里面 然后重启jmeter,如下图操作:          此时可以看到lib/ext目录里面多了......
  • 【DP优化技巧】2. 矩阵加快
    例题来看一道例题。P5024[NOIP2018提高组]保卫王国对于这道题,首先如果没有国王的询问,可以设定状态:\(f_{i,0/1}\)代表以\(i\)为根的子树里面,自己选/不选的最小花费。易得状态转移方程:\[f_{u,0}=\sum_{v\inson_u}f_{v,1}\\f_{u,1}=p_u+\sum_{v\inson_u}\min(f_{v,0},......
  • 数据结构优化DP
    数据结构优化DP参考题单CleaningShiftsS区间覆盖问题区间加区间最值线段树维护cin>>n>>m>>e;m++,e++;for(inti=1;i<=n;i++) c[i].in();T.build(1,1,e);sort(c+1,c+1+n,[](nodea,nodeb){ if(a.l==b.l)returna.r<b.r; returna......
  • 嵌入式开发之UDP网络编程
    1、TCP编程的函数API1.1、网络发送数据:send()/write()#include<sys/types.h>#include<sys/socket.h>ssize_tsend(intsockfd,constvoid*buf,size_tlen,intflags);#include<unistd.h>ssize_twrite(intfd,constvoid*buf,size_tcount);send()比write多......
  • AT_tdpc_knapsack ナップザック
    定义:\(A_c\)表示颜色为\(c\)的物品集合,\(g\otimesS\)表示背包\(g\)中插入集合\(S\)中的所有物品后的新背包。刚开始想的是对于每种颜色,求出各自背包,然后进行\(lim_c\)次\(O({lim_w}^2)\)的合并。显然T了,考虑漏了什么:本来操作\(g\otimesS\)是\(O(|g|\cdot......
  • [ABC234G] Divide a Sequence (DP+单调栈)题解
    分析题意十分简单,不难推出式子:$f_i=\sum_{j=1}^{i-1}f_j\times(\max_{k=j+1}^ia_k-\min_{k=j+1}^ia_k)$但我们考虑这个\(O(n^2)\)的东西显然是冲不过去的,所以必须优化转移。式子后面两块都是极值,这启发我们用单调栈解决。由于\(\max_{k=j+1}^i\)与\(\min_{k=......
  • 子集和dp
    子集和dp用处统计n维偏序,但是每一维的大小只能是2。计算子集权值之和。实际上以上两种问题是等价的。例如目前有一个集合:101(其中1表示有某个物品,0表示没有)。那该集合包涵的子集有4个:101,100,001,000。现在要把这4个集合的权值加起来。按照第二种理解(用处),我们可以一位一位地......