【APIO2010】巡逻

题目链接:https://www.luogu.com.cn/problem/P3629

题目大意:给定一棵树 , 给它增加 1 到 2 条边 , 且新增的边只能走一次 , 使得从 \(1\) 号节点出发 , 遍历其他所有点再回来的总路径最小

solution

首先 , 如果 \(k = 1\) 时 , 显然如果连接一条从 \(a_i\)\(b_i\) 的边 , 则从 \(a_i\)\(b_i\) 之间的路径将只要走 1 次 , 那么总路程将会减少 \(dist(a_i, b_i) - 1\) , 要使总路程最小 , 那么 $dist(a_i, b_i) $ 应该最大 , 显然应该连接这棵树直径的两端

如果 \(k = 2\) 时 , 则主要考虑第二条边该怎么连 , 如果连接一条从 \(a_j\)\(b_j\) 的边 , 那么从 \(a_j\)\(b_j\) 只要走一次 , 但第二次连边组成的环与第一次连边组成的环的重叠部分的长度 \(dis\) 要走两次 , 总路程将会减少 \(dist(a_j, b_j) - dis - 1\) , 这时候有一个非常巧妙的方法 , 将第一次连边组成的环上的边的权值取反 , 再求这棵树上的直径的长度即为上式的最大值

时间复杂度 : \(O(n)\)

一个提醒 : 当树上边权有为负数时 , 不能用两次搜索求直径 , 例如下图 , 从 4 可到达最远为 5 ,

但 5 并不是直径的端点 , 具体原因可以结合两次搜索的原理得出

[APIO2010]巡逻.PNG

code

#include<bits/stdc++.h>
using namespace std;
template <typename T> inline void read(T &FF) {
	int RR = 1; FF = 0; char CH = getchar();
	for(; !isdigit(CH); CH = getchar()) if(CH == '-') RR = -RR;
	for(; isdigit(CH); CH = getchar()) FF = FF * 10 + CH - 48;
	FF *= RR;
}
inline void file(string str) {
	freopen((str + ".in").c_str(), "r", stdin);
	freopen((str + ".out").c_str(), "w", stdout);
}
const int N = 5e5;
int n, k, lg, lt[N], vis[N], cl[N], f[N];
int now, fst[N], nxt[N], num[N], wi[N], d[N], ans = INT_MIN;
void add(int u, int v) {
	nxt[++now] = fst[u], fst[u] = now, num[now] = v, wi[now] = 1;
	nxt[++now] = fst[v], fst[v] = now, num[now] = u, wi[now] = 1;
}
int get_longest(int xi) {
	int res = 0, id = 0;
	queue<pair<int, int> > qi;
	for(int i = 1; i <= n; i++) lt[i] = i, vis[i] = 0;
	qi.push(make_pair(xi, 0));
	while(!qi.empty()) {
		pair<int, int> pi = qi.front(); qi.pop();
		vis[pi.first] = 1;
		if(pi.second > res) res = pi.second, id = pi.first;
		for(int i = fst[pi.first]; i; i = nxt[i])
			if(!vis[num[i]]) {
				lt[num[i]] = pi.first;
				qi.push(make_pair(num[i], pi.second + wi[i]));
			}
	}
	return id;	
}
void getlength_bfs() {
	int li = get_longest(1), ri = get_longest(li);
	cl[ri] = 1;
	while(lt[ri] != ri) {
		ri = lt[ri];
		cl[ri] = 1, lg++;
	}
}
void circle_mark() {
	for(int i = 1; i <= n; i++)
		if(cl[i]) {
			for(int j = fst[i]; j; j = nxt[j])
				if(cl[num[j]]) wi[j] = -1;
		}
	for(int i = 1; i <= n; i++) vis[i] = 0;
}
void getlength_dp(int xi) {
	vis[xi] = 1;
	for(int i = fst[xi]; i; i = nxt[i])
		if(!vis[num[i]]) {
			getlength_dp(num[i]);
			f[xi] = max(f[xi], d[xi] + d[num[i]] + wi[i]);
			d[xi] = max(d[xi], d[num[i]] + wi[i]);
		}
}
int main() {
	//file("");
	int u, v;
	read(n), read(k);
	for(int i = 1; i < n; i++)
		read(u), read(v), add(u, v);
	getlength_bfs();
	if(k == 1)
		cout << (n - 1 - lg) * 2 + lg + 1 << endl;
	else {
		circle_mark(); getlength_dp(1);
		for(int i = 1; i <= n; i++) ans = max(ans, f[i]);
		cout << (n - 1 - lg) * 2 + lg + 1 - ans + 1 << endl;
	}
	return 0;
}

posted @ 2020-02-02 20:04  MagicDuck  阅读(186)  评论(1编辑  收藏  举报