当前位置: 代码迷 >> 综合 >> 牛客多校第十场 Decrement on the Tree(树形DP,思维)
  详细解决方案

牛客多校第十场 Decrement on the Tree(树形DP,思维)

热度:56   发布时间:2024-02-09 09:03:13.0

链接:https://ac.nowcoder.com/acm/contest/5675/C
来源:牛客网

题目描述
You are given a tree. There are n vertices and n-1 edges. There is a non-negative weight for each edge in the tree. Every time, you can select two different vertices u, v, and subtract the weight of each edge on the path by 1. You want to make the weight of all edges become zero.
What is the minimum number of operations?
You also need to support modification of edge weights: Change the weight of p-th edge to w. You need to output the answer after each modification.
输入描述:
The first line contains two integers n, q(1\leq n, q\leq 10^5)n,q(1≤n,q≤10
5
).
In the following n-1 lines, each line contains three integers u,v, w(1\leq u,v \leq n, 1\leq w\leq 10^9)u,v,w(1≤u,v≤n,1≤w≤10
9
) - an edge (u,v) with weight w. Edges are indexed from 1 to n-1 in the order.
In the following q lines, each line contains two integers p , w(1\leq p\leq n-1, 1\leq w\leq 10^9)p,w(1≤p≤n?1,1≤w≤10
9
).
输出描述:
Output q+1 integers, the answer of the original tree and answers after each modification.
示例1
输入
复制
5 3
1 2 3
1 3 4
2 4 5
3 5 6
1 10
2 10
3 10
输出
复制
8
12
10
10

题意:
查询是将一条链上的边权都减1,要求所有边权都为0的最小操作次数。每次询问会修改一个边的边权,求查询的值。

思路:
看了题解也没有看懂,后面结合了一下过题的代码感觉算是懂了。
因为要考虑路径,所以一开始想的就是树分治或者树形DP(树链没学过)。


赛中初始的想法是:
假设确定了根节点,那么路径实际上有两种:经过根节点的,存在于子树的。
可以想到要肯定要尽量让路径自我抵消,不能抵消的部分,则要额外花费。

定义 f [ i ] f[i] 为以 i i 子树根节点,所有 i i 的出边在经过根节点的路径的花费。
来自父节点的边权可以抵消一部分花费,所以还要讨论来自父节点的抵消值。

如果 i i 的出边没有边权大于边权和的一半,则说明可以自我抵消,那么花费就是边权和一半(如果为奇数,则要额外加一)。否则就是花费就是最大边权。

  1. 如果子节点可以自我抵消且为奇数(额外加一了),则先减掉这个1。
  2. 如果可以自我抵消则直接剪掉父节点边权一半。
  3. 否则就先把最大边权砍成一半,那么就可以自我抵消了,再考虑1,2的部分。

继续向下递归,结果就是 f [ i ] ∑f[i]

每个状态 f [ i ] f[i] 只与父节点权值和 i i 的出边有关,所以可以应对多次询问。
这个转移难讨论的点就是父节点的抵消花费的计算,讨论过程很容易出错。


结合题解的想法:

但实际上,这个DP可以优化,我们可以进行换根操作。
定义 f [ i ] f[i] 为以 i i 根节点,所有 i i 的出边在经过根节点的路径的花费。
这个定义的好处是我们无需再讨论来自父节点的抵消值,唯一要考虑的就是点 i i 的出边。那么每次修改操作只需要考虑对点 i i 出边的影响,这个过程我们可以直接维护SET搞,这就对应的题解的写法。


所以标签虽然写的是树形DP,但只是我个人的一些想法,我觉得这个题跟具体算法无关,更多还是考验思维吧。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <vector>
#include <queue>
#include <iostream>
#include <map>
#include <string>
#include <set>using namespace std;typedef long long ll;const int mod = 1e9 + 7;
const int maxn = 1e5 + 7;int X[maxn],Y[maxn],Z[maxn];
multiset<int>s[maxn];
ll sum[maxn];int main() {int n,q;scanf("%d%d",&n,&q);for(int i = 1;i < n;i++) {scanf("%d%d%d",&X[i],&Y[i],&Z[i]);s[X[i]].insert(Z[i]);s[Y[i]].insert(Z[i]);sum[X[i]] += Z[i];sum[Y[i]] += Z[i];}ll ans = 0;for(int i = 1;i <= n;i++) {ans += max(sum[i] & 1,(*(--s[i].end())) * 2 - sum[i]);}printf("%lld\n",ans / 2);for(int i = 1;i <= q;i++) {int x,y;scanf("%d%d",&x,&y);ans -= max(sum[X[x]] & 1,*(--s[X[x]].end()) * 2 - sum[X[x]]);ans -= max(sum[Y[x]] & 1,*(--s[Y[x]].end()) * 2 - sum[Y[x]]);s[X[x]].erase(s[X[x]].find(Z[x]));s[Y[x]].erase(s[Y[x]].find(Z[x]));sum[X[x]] -= Z[x];sum[Y[x]] -= Z[x];Z[x] = y;sum[X[x]] += Z[x];sum[Y[x]] += Z[x];s[X[x]].insert(y);s[Y[x]].insert(y);ans += max(sum[X[x]] & 1,*(--s[X[x]].end()) * 2 - sum[X[x]]);ans += max(sum[Y[x]] & 1,*(--s[Y[x]].end()) * 2 - sum[Y[x]]);printf("%lld\n",ans / 2);}return 0;
}
  相关解决方案