• 【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;
    }
    
    
  • 相关阅读:
    应用文档iOS, 在应用之间共享文档。
    项目范围项目管理项目范围管理
    网站教程一些学习网站
    布局文件Android ListView入门知识各种Adapter配合使用
    随波逐流希望2013年半年小总结(复杂艰难的半年)
    安全微软微软安全新闻聚焦双周刊第三十二期
    对象类《大话设计模式》部分模式总结(一):
    循环跳转JAVA Continue实例详解
    节点程序寻找链表倒数第k个节点
    box2d编译
  • 原文地址:https://www.cnblogs.com/Sakura-TJH/p/YBTOJ_GXJJ_21167.html
Copyright © 2020-2023  润新知