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

__EOF__

本文作者あおいSakura
本文链接https://www.cnblogs.com/Sakura-TJH/p/YBTOJ_GXJJ_21167.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   あおいSakura  阅读(38)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示