2019暑假篇——巨樹(二分答案)

目錄

一.題目

1.描述

2.輸入

3.輸出

4.樣例輸入

5.樣例輸出

 二.題解

1.算法

2.細節

三.代碼

謝謝!


一.題目

1.描述

 給出一顆 N 個節點的樹和 M 個關鍵節點以及無線數據傳輸裝置的數量 K, 樹邊長度爲 1。 你需要把這 K 個無線數據傳輸裝置安裝到樹的一些節點上, 並讓關鍵節點離最近無線 傳輸裝置的距離最大值最小, 求出這個最小值。 此處, 樹上節點 A 和 B 之間的距離指 AB 兩點間路徑長度和。

2.輸入

第一行輸入三個正整數 N, M 和 K。 接下來一行輸入 M 個正整數, 表示關鍵點的編號。 接下來 N-1 行, 每行兩個正整數 a, b, 表示節點 a 和節點 b 之間有一條邊。

3.輸出

輸出一行一個正整數表示你的答案。

4.樣例輸入

5 2 1
1 4
1 2
1 3
2 4
4 5

5.樣例輸出

1

 二.題解

1.算法

乍一看,這個答案是具有單調性的,並且有確切的範圍(0~n-1),果斷用二分答案的方法雖然考試時沒想到

但是二分裏面的check怎麼寫呢?這是最大的難點。

我是這樣想的:

搜索這一顆樹,在回溯的時候判斷以這個點爲根的子樹裏離這個點最遠的且沒被覆蓋的關鍵點的距離,如果這個距離和你二分限制的距離相等了,那麼你就必須在這個點放一個基站。就這樣做下去,如果最終用的基站數量大於了k,就否定這個二分的限制。

舉個例子(就拿樣例來說):

(標橙的是關鍵節點)假設限制距離爲1,從根節點1開始搜,先搜到2,再往下搜,搜到4,再往下搜,搜到5,這是考慮5的回溯,因爲在5的子樹裏沒有關鍵節點,所以這時最遠關鍵結點的距離還是0;回溯到4,發現只有4它本生是關鍵節點,這是關鍵節點的距離還是0;回溯到2,關鍵節點的最遠距離就變成了1,這1等於了我們限制的距離,並且離這個點距離爲1的範圍內沒有基站,所以必須在節點2放1個基站;回溯到節點1,再往節點3搜,節點3不是關鍵節點,直接回溯,發現節點1是一個關鍵節點,並且離這個點距離爲1的範圍有基站,所以不管它。最終就設了一個基站,最短距離爲1.

2.細節

這道題不光要會算法,細節也特別多。主要分佈在check內的dfs裏邊。

1.噹噹前節點的子樹裏的關鍵節點都被覆蓋或沒有時,我把紀錄關鍵節點的距離的變量(max_d)標爲-1,只有當max_d不爲-1時,回溯時才能加1:

if (max_d != -1) max_d ++;

2.如果當前節點是關鍵節點,在回溯時要注意,如果max_d不爲-1,就不管它,否則要把max_d賦爲0:

if (vis[x]) max_d = max (max_d, 0);

3.用min_k記錄最近的一個基站的距離,如果關鍵節點和最近的基站在樹同側,限制的距離-min_k=限制的距離-當前關鍵節點到最近基站的距離,如果減出來大於等於0,就行,因爲當前關鍵節點的最遠距離爲0;如果關鍵節點和最近基站在異側,畫一張圖就理解了:

(回溯到節點1)min_k = 節點1到節點2的距離,當前關鍵節點的最遠距離爲1,限制的最遠距離-min_k\geq1就行。

if (now_way - min_k >= max_d) max_d = -1;

4.如果當前最遠的關鍵節點距離等於了限制距離,就必須在這裏放基站

if (now_way == max_d){
		max_d = -1;
		min_k = 0;
		cnt ++;
	}

其他的就沒有什麼想說的了,這道題目就解決了。

三.代碼

#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>
using namespace std;
#define M 200005
int n, m, k, gj, ans = 0x3f3f3f3f, now_way, cnt;
vector <int> G[M];
bool vis[M];
pair <int, int> dfs (int x, int fa){
	int min_k = 0x3f3f3f3f, max_d = -1;
	for (int i = 0; i < G[x].size (); i ++){
		int u = G[x][i];
		if (u != fa){
			pair <int, int> tmp = dfs (u, x);
			min_k = min (min_k, tmp.first), max_d = max (max_d, tmp.second);//找關鍵節點最大距離和最近基站的距離
		}
	}
	min_k ++;
	if (max_d != -1) max_d ++;
	if (vis[x]) max_d = max (max_d, 0);
	if (now_way - min_k >= max_d) max_d = -1;
	if (now_way == max_d){
		max_d = -1;
		min_k = 0;
		cnt ++;
	}
	return make_pair (min_k, max_d);
}
bool check (int max_way){
	cnt = 0;
	now_way = max_way;
	pair <int, int> tmp = dfs (1, 0);
	if (tmp.second != -1)
		cnt ++;
	if (cnt > k)
		return 0;
	return 1;
}
int main (){
	scanf ("%d %d %d", &n, &m, &k);
	for (int i = 1; i <= m; i ++){
		scanf ("%d", &gj);
		vis[gj] = 1;
	}
	int a, b;
	for (int i = 1; i < n; i ++){
		scanf ("%d %d", &a, &b);
		G[a].push_back (b);
		G[b].push_back (a);
	}
	int l = 0, r = n - 1, mid;
	while (l <= r){//二分
		mid = (l + r) / 2;
		if (check (mid)){
			ans = min (ans, mid);
			r = mid - 1;
		}
		else
			l = mid + 1;
	}
	printf ("%d\n", ans);
	return 0;
}

謝謝!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章