[APIO2010]巡逻

题目:巡逻

网址:https://www.luogu.com.cn/problem/P3629

题目描述

在一个地区中有\(n\)个村庄,编号为\(1, 2, ..., n\)。有\(n–1\)条道路连接着这些村庄,每条道路刚好连接两个村庄,从任何一个村庄,都可以通过这些道路到达其 他任一个村庄。每条道路的长度均为\(1\)个单位。 为保证该地区的安全,巡警车每天要到所有的道路上巡逻。警察局设在编号为\(1\)的村庄里,每天巡警车总是从警察局出发,最终又回到警察局。 下图表示一个有\(8\)个村庄的地区,其中村庄用圆表示(其中村庄\(1\)用黑色的圆表示),道路是连接这些圆的线段。为了遍历所有的道路,巡警车需要走的距 离为\(14\)个单位,每条道路都需要经过两次。

[APIO2010]巡逻_第1张图片

为了减少总的巡逻距离,该地区准备在这些村庄之间建立\(K\)条新的道路, 每条新道路可以连接任意两个村庄。两条新道路可以在同一个村庄会合或结束 (见下面的图例(c))。 一条新道路甚至可以是一个环,即,其两端连接到同一 个村庄。 由于资金有限,\(K\)只能是\(1\)\(2\)。同时,为了不浪费资金,每天巡警车必须 经过新建的道路正好一次。 下图给出了一些建立新道路的例子:

[APIO2010]巡逻_第2张图片

在(a)中,新建了一条道路,总的距离是\(11\)。在(b)中,新建了两条道路,总 的巡逻距离是 \(10\)。在(c)中,新建了两条道路,但由于巡警车要经过每条新道路 正好一次,总的距离变为了\(15\)。 试编写一个程序,读取村庄间道路的信息和需要新建的道路数,计算出最佳 的新建道路的方案使得总的巡逻距离最小,并输出这个最小的巡逻距离。

输入格式

第一行包含两个整数\(n,K(1≤K≤2)\)。接下来\(n–1\) 行,每行两个整数\(a,b\), 表示村庄\(a\)\(b\)之间有一条道路\((1≤a,b≤n)\)

输出格式

输出一个整数,表示新建了\(K\)条道路后能达到的最小巡逻距离。

输入输出样例

输入 #1

8 1 
1 2 
3 1 
3 4 
5 3 
7 5 
8 5 
5 6

输出 #1

11

输入 #2

8 2 
1 2 
3 1 
3 4 
5 3 
7 5 
8 5 
5 6

输出 #2

10

输入 #3

5 2 
1 2 
2 3 
3 4 
4 5

输出 #3

6

说明/提示

\(10%\)的数据中,\(n≤1000,K=1\)

\(30%\)的数据中,\(K=1\)

\(80%\)的数据中,每个村庄相邻的村庄数不超过\(25\)

\(90%\)的数据中,每个村庄相邻的村庄数不超过\(150\)

\(100%\)的数据中,\(3≤n≤100,000, 1≤K≤2\)


我们先不考虑加边的情况。如果正常从\(1\)号结点走经过所有的边后返回,那么至少要走的路径长度为多少?很显然 \((n-1)*2\)。我们可以考虑边\((u,v)\),当从\(u\)遍历至\(v\)的时候,由于每个节点到根节点只有唯一一条简单路径,因此要返回的时候必须经过该边。每条边最少经过两次。

我们新建道路有这样的原则:一定要成环,且只允许经过一次。

  • 加一条边。这种情况其实不难,考虑到每形成一个环的时候环上的所有边仅经过一次,因而我们仅需要找到树上最长链(树的直径),不妨设为\(D_1\),答案即为\((n-1)*2-D_1+1\)
  • 加两条边。设第二条边所选的树链长度为\(D_2\)。事实上这个时候如果构成的两个环相重叠,不妨设重叠了\(p\)条边,则答案为\((n-1)*2-D_1+1-D_2+1+x*2\)。我们对该式进行整理得:\(n*2-(D_2-x*2)-D_1\)。这启发我们应该将所有重叠的边进行处理。给出一个漂亮的做法:树的直径找到,将上面的边权赋为\(-1\),再跑一次树的直径,长度即为\(D_2\)

C ++ AC代码

#include
#include
#include
#include
#include
#include
using namespace std;
const int SIZE = 100000 + 5;
vector  G[SIZE];
bool vis[SIZE] = {}, chosen[SIZE];//vis 用于BFS求树的直径, chosen 记录该节点是否在树的直径
int n, k, prev[SIZE], dis[SIZE], dp[SIZE];//prev 记录结点的前驱 
int d1 = 0, d2 = 0;
void bfs()//第一遍:找直径
{
	queue  Q;
	while(!Q.empty()) Q.pop();
	memset(dis, 0, sizeof(dis));
	Q.push(1);
	int u, v, s, t;
	while(!Q.empty())
	{
		u = Q.front();
		Q.pop();
		for(int i = 0; i < G[u].size(); ++ i)
		{
			int v = G[u][i];
			if(!vis[v]) 
			{
				Q.push(v);
				vis[v] = true;
			}
		}
	}
	memset(vis, false, sizeof(vis));
	s = u;
	Q.push(s);
	while(!Q.empty())
	{
		u = Q.front();
		Q.pop();
		for(int i = 0; i < G[u].size(); ++ i)
		{
			v = G[u][i];
			if(vis[v]) continue;
			vis[v] = true;
			Q.push(v);
			dis[v] = dis[u] + 1;
			prev[v] = u;
		}
	}
	memset(chosen, false, sizeof(chosen));
	t = u;
	d1 = dis[t];
	chosen[s] = true;
	do
	{
		chosen[u] = true;
		u = prev[u];
	} while(u != s);
	return;
}
void dfs(int u, int Fa)
{
	for(int i = 0; i < G[u].size(); ++ i)
	{
		int v = G[u][i], op;
		if(v == Fa) continue; 
		if(chosen[u] && chosen[v]) op = -1;
		else op = 1;
		dfs(v, u);
		d2 = max(d2, dp[u] + dp[v] + op);
		dp[u] = max(dp[u], dp[v] + op);
	}
	return;
}
int main()
{
	scanf("%d %d", &n, &k);
	for(int i = 0; i < n; ++ i) G[i].clear();
	
	int x, y;
	for(int i = 1; i < n; ++ i)
	{
		scanf("%d %d", &x, &y);
		G[x].push_back(y), G[y].push_back(x);
	}
	bfs();
	if(k > 1)
	{
		memset(dp, 0, sizeof(dp)); dfs(1, 0);
		printf("%d\n", (n << 1) - d1 - d2);
	}
	else printf("%d\n", (n << 1) - d1 - 1);
	return 0;
}

总结回顾

将边权更改成为我们希望的数值,这是值得借鉴模仿的地方。

参考文献

  • 李煜东 《算法竞赛之进阶指南》0X63

你可能感兴趣的:([APIO2010]巡逻)