P3629 [APIO2010] DFS + 树形 DP
作者:互联网
题意
题解
当 K = 0 K=0 K=0,遍历整棵树,每条边必然被递归依次,回溯一次,故路线总长度为 2 × ( n − 1 ) 2\times (n-1) 2×(n−1)。
当 K = 1 K=1 K=1,由于新增的边必须正好经过一次,那么由于新增边而构成的环需要遍历一次,则环上的树边经过次数都减少一次,那么目标为最大化环上的树边,那么树的直径即可减小的最大权值。
当 K = 2 K=2 K=2,新增的边又构成一个环,若两条新增道路构成的环不重叠,那对于新增的环上的树边贡献依然是 − 1 -1 −1,若存在重叠,由于环上的边需遍历一次,故贡献为 1 1 1。目标是使总距离最小,那么对于 K = 1 K=1 K=1 情况的环上树边的边权取负,再次计算树的直径,即此时可减小的最大权值。
需要证明迭代求树的直径可以求解 K = 2 K=2 K=2 情况下的答案。反证法,问题可以转化为求树上无相同边的 2 2 2 条路径的最大边权和,假设 2 2 2 条路径完全不经过树的直径,那么用树的直径替换其中一条路径可以获得更大的边权和;假设 2 2 2 条路径部分与树的直径重叠,根据树的直径的最长性,可以替换得到不小于当前边权和的 2 2 2 条新路径。故树的直径一定处于新增边所在的环上。
处理环上边权取负时,使用 D F S DFS DFS 求解树的直径,记录直径上的边;但 D F S DFS DFS 无法处理边权为负的情况,于是使用树形 D P DP DP 求解 K = 2 K=2 K=2 情况下树的直径。
#include <bits/stdc++.h>
using namespace std;
#define maxn 100005
int N, K, d[maxn], pree[maxn];
int head[maxn << 1], nxt[maxn << 1], to[maxn << 1], cost[maxn << 1], tot = 1;
void add(int x, int y, int z)
{
to[++tot] = y, cost[tot] = z, nxt[tot] = head[x], head[x] = tot;
}
void dfs(int x, int f, int &t)
{
if (d[x] > d[t])
t = x;
for (int i = head[x]; i; i = nxt[i])
{
int y = to[i], z = cost[i];
if (y != f)
d[y] = d[x] + z, pree[y] = i, dfs(y, x, t);
}
}
void dp(int x, int f, int &t)
{
for (int i = head[x]; i; i = nxt[i])
{
int y = to[i], z = cost[i];
if (y != f)
{
dp(y, x, t);
t = max(t, d[x] + d[y] + z);
d[x] = max(d[x], d[y] + z);
}
}
}
int main()
{
scanf("%d%d", &N, &K);
for (int i = 1; i < N; ++i)
{
int a, b;
scanf("%d%d", &a, &b);
add(a, b, 1);
add(b, a, 1);
}
int s = 1, t = 0, res = (N - 1) << 1;
dfs(s, 0, t);
s = t, t = 0, d[s] = pree[s] = 0;
dfs(s, 0, t);
res -= d[t] - 1;
if (K == 2)
{
for (int i = pree[t]; i; i = pree[to[i ^ 1]])
cost[i] = cost[i ^ 1] = -1;
memset(d, 0, sizeof(d));
t = 0;
dp(1, 0, t);
res -= t - 1;
}
printf("%d\n", res);
return 0;
}
标签:head,环上,int,APIO2010,DFS,P3629,cost,直径,DP 来源: https://blog.csdn.net/neweryyy/article/details/111186872