题目链接:
题意:
给定一棵树,根节点为1,每条边都有一个权值。节点 i 到节点 j 的花费为这两点间路径权值和的平方。现在从每个点到根节点,可以在路径的任何点停留,每次停留cost+=这两点间路径权值和的平方+P(最后一步跳到根节点不需要+P),使该cost最小化。最后求所有点的cost的最大值。
思路:
每个点到根节点都是一条唯一路径:
设dp[i]:表示节点 i 到根节点的花费的最小值。
设sum[i]:表示路径上权值的前缀和。
(0<=j<i)
复杂度O(n^2),对该路径上的权值进行斜率dp优化O(n):
设 j>k,且 j 优于 k :
设x=sum[i],y=dp[i]+sum[i]^2,上述不等式左边即为斜率。
可知:
当 时,
j 优于 k 且 j 优于 i 。
因此维护一个下凸:

因为sum[i]单调递增,所以维护单调队列,每次找到第一个 j 和 j+1 的斜率>2sum[i],j 即为最佳跳转点。
本题最关键的点在于单调队列的还原。如果树的每条链都单独计算,会有很多重复计算的边,复杂度会远超O(n)。因此需要回溯到一个点的时候把单调队列还原。
做法是dfs树的时候给每个点一个dfs序。维护一个栈,当单调队列里有点被弹出了,就把该点在单调队列的位置,值和被弹出的时间戳压入栈。当回溯到该点时,把栈中dfs序在该点之后的点还原,再把单调队列此时的 left 和 right 还原。
因为最后一步跳到根节点不需要+P,所以按上述式子多算了一次P,答案剪掉即可。
Code:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAX = 2e5 + 50;
typedef struct {
int to, val;
}Point;
typedef struct {
int pos;
int val;
int time;
}Node;
int n, p;
vector<Point>mp[MAX];
ll dp[MAX], sum[MAX];
int Left, Right;
int q[MAX];
int dfstime;
stack<Node>s;
ll up(int j, int k) {
return (dp[j] + sum[j] * sum[j]) - (dp[k] + sum[k] * sum[k]);
}
ll down(int j, int k) {
return (sum[j] - sum[k]);
}
void dfs(int root, int fa)
{
int nowtime = ++dfstime;
while (Left < Right&&up(q[Left + 1], q[Left]) <= 2 * sum[root] * down(q[Left + 1], q[Left]))
Left++;
int front = q[Left];
dp[root] = dp[front] + (sum[root] - sum[front])*(sum[root] - sum[front]) + p;
while (Left < Right&&up(q[Right], q[Right - 1])*down(root, q[Right]) >= up(root, q[Right])*down(q[Right], q[Right - 1])) {
s.push(Node{ Right,q[Right],nowtime });
Right--;
}
q[++Right] = root;
int le = Left, re = Right;
for (int i = 0; i < mp[root].size(); i++) {
if (mp[root][i].to == fa) continue;
sum[mp[root][i].to] = sum[root] + mp[root][i].val;
Left = le, Right = re;
while (!s.empty()) {
Node tmp = s.top();
if (tmp.time <= nowtime) break;
q[tmp.pos] = tmp.val;
s.pop();
}
dfs(mp[root][i].to, root);
}
return;
}
int main()
{
int T;
scanf("%d", &T);
while (T--)
{
while (!s.empty())
s.pop();
scanf("%d%d", &n, &p);
for (int i = 0; i <= n; i++)
mp[i].clear();
for (int i = 1; i < n; i++) {
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
mp[u].push_back(Point{ v, w });
mp[v].push_back(Point{ u, w });
}
dfstime = 0;
dp[1] = 0;
sum[1] = 0;
Left = 1, Right = 1;
q[1] = 1;
int le = Left, re = Right;
for (int i = 0; i < mp[1].size(); i++) {
sum[mp[1][i].to] = sum[1] + mp[1][i].val;
Left = le, Right = re;
while (!s.empty()) {
s.pop();
}
dfs(mp[1][i].to, 1);
}
ll ans = 0;
for (int i = 1; i <= n; i++) {
ans = max(ans, dp[i] - p);
}
printf("%lld\n", ans);
}
return 0;
}
本文介绍了一种解决特定树形结构问题的算法——树形DP结合斜率优化技术,通过实例详细解析了如何利用单调队列进行状态转移优化,以达到O(n)的时间复杂度。

被折叠的 条评论
为什么被折叠?



