【ybtoj高效进阶 21167】旅游计划(基环树)(DP)(单调队列)
旅游计划
题目链接:ybtoj高效进阶 21167
题目大意
给你一个基环树。
然后你可以在环上选一个边删掉,然后使它变成树。
一棵树的贡献是它里面最长路径。
然后要你求可以有的树的贡献的最大值。
思路
首先第一步考虑找到环。
(用类似 tarjan 找环)
然后对于环上的每个点,它们会挂着一些树,那我们就要求它这个树内部的最长路径(可能就是最优答案),或者从根到里面的一条最长路径。(这个可以 DP 得出)
那接着就是选两个点,它们之间的路径和加它们到自己树里面的最长路径,要这个和的最大值。
那这个直接 DP(记得要来取两种各 DP 一次)当然是会超时的,我们考虑一下如何优化。
由于是环上 DP,考虑复制。
然后观察到后面的区间长度是固定的(就是环的大小),所以我们可以用单调队列优化。
代码
#include<cstdio>
#include<iostream>
#define ll long long
using namespace std;
struct node {
ll x;
int to, nxt;
}e[400005];
int n, x, y, sta[200005], cir[200005], cirn;
bool incir[200005], in[200005], fd;
int KK, le[200005];
ll z, ansin, f[200005], g[200005], l[200005];
ll ansout, f1[200005], f2[200005], fg1[200005];
ll g1[200005], g2[200005], fg2[200005];
void add(int x, int y, ll z) {
e[++KK] = (node){z, y, le[x]}; le[x] = KK;
e[++KK] = (node){z, x, le[y]}; le[y] = KK;
}
void get_cir(int now, int edge) {
sta[++sta[0]] = now;
in[now] = 1;
for (int i = le[now]; i; i = e[i].nxt)
if (i != (edge ^ 1)) {
if (!in[e[i].to]) {
get_cir(e[i].to, i);
if (fd) return ;
}
else {
incir[e[i].to] = 1;
cir[++cirn] = e[i].to;
while (sta[sta[0]] != e[i].to) {
incir[sta[sta[0]]] = 1;
cir[++cirn] = sta[sta[0]];
sta[0]--;
}
fd = 1; return ;
}
}
sta[0]--;
}
void dfs0(int now, int father) {//DP 求出最长路径
for (int i = le[now]; i; i = e[i].nxt)
if (e[i].to != father && !incir[e[i].to]) {
dfs0(e[i].to, now);
if (f[e[i].to] + e[i].x > f[now]) {
g[now] = f[now]; f[now] = f[e[i].to] + e[i].x;
}
else if (f[e[i].to] + e[i].x > g[now]) g[now] = f[e[i].to] + e[i].x;
}
ansin = max(ansin, f[now] + g[now]);
}
int main() {
// freopen("travel.in", "r", stdin);
// freopen("travel.out", "w", stdout);
// freopen("read.txt", "r", stdin);
KK = 1;
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d %d %lld", &x, &y, &z);
add(x, y, z);
}
get_cir(1, 0);
for (int i = 1; i <= n; i++)
if (incir[i]) dfs0(i, 0);
cir[0] = cir[cirn];//找到环
int lst = 0;
for (int i = 0; i < cirn; i++) {
for (int j = le[cir[i]]; j; j = e[j].nxt)
if (e[j].to == cir[i + 1] && lst != j && (lst ^ 1) != j) {
l[i] = e[j].x; lst = j;
break;
}
}
for (int i = 1; i <= cirn; i++)//前缀和
f1[i] = f1[i - 1] + l[i - 1];
for (int i = cirn - 1; i >= 0; i--)
f2[i] = f2[i + 1] + l[i];
ll maxn = -1e18;//单调队列优化DP
for (int i = 1; i <= cirn; i++) {
if (maxn != -1e18) g1[i] = max(g1[i - 1], maxn + f1[i] + f[cir[i]]);
maxn = max(maxn, f[cir[i]] - f1[i]);
fg1[i] = max(fg1[i - 1], f[cir[i]] + f1[i]);
}
maxn = -1e18;
g2[cirn] = f[cir[cirn]];
fg2[cirn] = f[cir[cirn]];
for (int i = cirn; i >= 1; i--) {
if (maxn != -1e18) g2[i] = max(g2[i + 1], maxn + f2[i] + f[cir[i]]);
maxn = max(maxn, f[cir[i]] - f2[i]);
fg2[i] = max(fg2[i + 1], f[cir[i]] + f2[i]);
}
ansout = 1e18;
for (int i = 2; i <= cirn; i++)
ansout = min(ansout, max(max(g1[i - 1], g2[i]), fg1[i - 1] + fg2[i]));
printf("%lld", max(ansin, ansout));
return 0;
}