[APIO2010] 巡逻

题面

巡逻

题目大意

在一颗边长为 \(1\) 的树上,添加一条或两条长度为 \(1\) 的路径,使遍历完整幅图,且经过添加的路径正好一次,并回到起点的路径长度最短,并输出这个长度。

题解

不加的情况

根据深搜的特点,每条路径必定会经过 \(2\) 次,即第一次访问和回溯。所以在不加的时候,最短路径一定是 \(2 * (n - 1)\)

加一条边的情况

因为不加时每条边都要经过两次,加了边之后的好处就是,可以不走原路返回,改为直接返回某一个点,于是要求最短的路径,就是要让节省的路径长度最长。而树中最长的路径就是树的直径,所以我们对于直径就不走原路返回,改为通过新添加的一条长度为 \(1\) 的路径直接返回直径的另一端。
所以答案为:

\[2*(n-1)-L+1 \]

其中 \(L\) 为直径的长度,加一是因为我们走了一条长度为 \(1\) 的道路。

加两条边的情况

考虑在加一条边的基础上再加一条边,使路径最短。
那么新加的这条边为了使路径最短,思考方式自然和加一条边的时候相同。那么答案就是:

\[2*(n-1)-L_1+1 - L_2 + 1$$ $$2*n-L_1-L_2 \]

但是,新加的这条边形成的环可能会与第一次加的边形成的环有些地方重合。为了保证正确,我们自然不能多减去这些重合的路径的长度。
于是我们考虑把上一棵树中的直径上的每一条边取反。
这样如果有重复的路径的话,减去 \(-1\) 就相当于把重复减去的路径加了回来。所以最终的答案才是正确的。

代码

#include<cstdio>
#include<iostream>
#include<cstring>
using std::max; 
const int N = 1e5 + 5;
struct edge {
	int next,to,w;
}a[N << 1];
int head[N],dis[N],pre[N],prew[N],flag[N],n,k,a_size = 1,L1,L2;
bool vis[N];
inline void add(int u,int v,int w) {
	a[++a_size] = (edge){head[u],v,w};
	head[u] = a_size;
	a[++a_size] = (edge){head[v],u,w};
	head[v] = a_size;
}
void dfs1(int x) {
	vis[x] = true;
	for(int i = head[x]; i; i = a[i].next) {
		int y = a[i].to;
		if(vis[y]) continue;
		dis[y] = dis[x] + a[i].w; dfs1(y);
	}
}
void dfs2(int x) {
	vis[x] = true;
	for(int i = head[x]; i; i = a[i].next) {
		int y = a[i].to;
		if(vis[y]) continue;
		dis[y] = dis[x] + a[i].w; 
		pre[y] = x; prew[y] = a[i].w; 
		flag[y] = i; dfs2(y);
	}
}
void init() {
	memset(vis,false,sizeof(vis));
	memset(dis,0,sizeof(dis));
}
void dp(int x,int &ans) {
	vis[x] = true;
	for(int i = head[x]; i; i = a[i].next) {
		int y = a[i].to;
		if(vis[y]) continue;
		dp(y,ans);
		ans = max(ans,dis[x] + dis[y] + a[i].w);
		dis[x] = max(dis[x],dis[y] + a[i].w);
	}
}
inline int read() {
	int x = 0,flag = 1;
	char ch = getchar();
	while(ch < '0' || ch > '9'){if(ch == '-')flag = -1;ch = getchar();}
	while(ch >='0' && ch <='9'){x = (x << 3) + (x << 1) + ch - 48;ch = getchar();}
	return x * flag;
}
int main() {
	n = read(),k = read();
	for(int i = 1; i < n; i++) {
		int u = read(),v = read();
		add(u,v,1); 
	}
	dfs1(1); int maxn = -1e9,p;
	for(int i = 1; i <= n; i++) {
		if(dis[i] > maxn) {
			maxn = dis[i];
			p = i;
		}
	} init();
	dfs2(p); maxn = -1e9; int q;
	for(int i = 1; i <= n; i++) {
		if(dis[i] > maxn) {
			maxn = dis[i];
			q = i;
		}
	}
	while(q != p) {
		L1 += prew[q];
		a[flag[q]].w = a[flag[q] ^ 1].w = -1;
		q = pre[q];
	}
	if(k == 1) {
		printf("%d",2 * (n - 1) - L1 + 1);
		return 0;
	} init(); 
	dp(1,L2);
	printf("%d",2 * n - L1 - L2);
	return 0;
}

posted @ 2021-05-29 19:57  init-神眷の樱花  阅读(53)  评论(0)    收藏  举报