【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;
}

posted @ 2021-10-27 09:32  あおいSakura  阅读(36)  评论(0编辑  收藏  举报