[APIO2010]巡邏

題目:巡邏

網址:https://www.luogu.com.cn/problem/P3629ios

題目描述

在一個地區中有\(n\)個村莊,編號爲\(1, 2, ..., n\)。有\(n–1\)條道路鏈接着這些村莊,每條道路恰好鏈接兩個村莊,從任何一個村莊,均可以經過這些道路到達其 他任一個村莊。每條道路的長度均爲\(1\)個單位。 爲保證該地區的安全,巡警車天天要到全部的道路上巡邏。警察局設在編號爲\(1\)的村莊裏,天天巡警車老是從警察局出發,最終又回到警察局。 下圖表示一個有\(8\)個村莊的地區,其中村莊用圓表示(其中村莊\(1\)用黑色的圓表示),道路是鏈接這些圓的線段。爲了遍歷全部的道路,巡警車須要走的距 離爲\(14\)個單位,每條道路都須要通過兩次。算法

image

爲了減小總的巡邏距離,該地區準備在這些村莊之間創建\(K\)條新的道路, 每條新道路能夠鏈接任意兩個村莊。兩條新道路能夠在同一個村莊會合或結束 (見下面的圖例(c))。 一條新道路甚至能夠是一個環,即,其兩端鏈接到同一 個村莊。 因爲資金有限,\(K\)只能是\(1\)\(2\)。同時,爲了避免浪費資金,天天巡警車必須 通過新建的道路正好一次。 下圖給出了一些創建新道路的例子:安全

image

在(a)中,新建了一條道路,總的距離是\(11\)。在(b)中,新建了兩條道路,總 的巡邏距離是 \(10\)。在(c)中,新建了兩條道路,但因爲巡警車要通過每條新道路 正好一次,總的距離變爲了\(15\)。 試編寫一個程序,讀取村莊間道路的信息和須要新建的道路數,計算出最佳 的新建道路的方案使得總的巡邏距離最小,並輸出這個最小的巡邏距離。spa

輸入格式

第一行包含兩個整數\(n,K(1≤K≤2)\)。接下來\(n–1\) 行,每行兩個整數\(a,b\), 表示村莊\(a\)\(b\)之間有一條道路\((1≤a,b≤n)\)code

輸出格式

輸出一個整數,表示新建了\(K\)條道路後能達到的最小巡邏距離。blog

輸入輸出樣例

輸入 #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\)get

\(30%\)的數據中,\(K=1\)string

\(80%\)的數據中,每一個村莊相鄰的村莊數不超過\(25\)io

\(90%\)的數據中,每一個村莊相鄰的村莊數不超過\(150\)class

\(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<iostream>
#include<cstring>
#include<vector>
#include<cstdio>
#include<cmath>
#include<queue>
using namespace std;
const int SIZE = 100000 + 5;
vector <int> 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 <int> 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
相關文章
相關標籤/搜索