題解:AT_abc368_d[ABC368D] Minimum Steiner Tree

海石竹跃發表於2024-12-06

題目大意

題目
給定一棵由 $N$ 個節點組成的無根樹,刪除其中的一些點和邊,使剩下的點和邊仍然能夠組成一棵樹,且包含給定的 $K$ 個特殊點,問最少剩下幾個點。

思路

我們可以發現,這棵無根樹的根必須是給定的特殊點之一,不然根節點就可以刪除,答案就不是最優。所以我們使用深度優先搜尋遍歷這棵樹,根節點為給定的點中的任意一個,不妨使用第一個讀入的特殊點。深搜函式中,我們要考慮一下這個點取還是不取。如果這個點的後代中有特殊點,那麼這個點是一定要取的,因為樹是一個無向無環連通圖,要是不取的話我們就沒有辦法取到那個特殊點了。

虛擬碼/程式碼框架

我們用 $f_i$ 表示 $i$ 號節點要不要取,$f_i=1$ 表示要取,$f_i=0$ 則表示不取。
$dfs$ 函式:

  • 有兩個引數,分別是當前點 $x$ 和父節點 $fa$。
  • 如果 $x$ 是特殊點,那麼一定要取。
  • 列舉 $x$ 的鄰居 $y$,如果不是父親(即是兒子),那麼遞迴,考慮兒子要不要取,$f_x = f_x \lor f_y$。

主函式:

  • 讀入,跑一遍深搜,統計必須取的點的個數。

程式碼

#include <cstdio> 
#include <iostream> 
#include <algorithm>
#include <vector>
using namespace std;

int n, k, cnt; // n,k含義見題面,cnt表示答案
int f[200010]; // 要不要取
vector<int> g[200010]; // 儲存

void dfs(int x, int fa) // x是當前節點,fa是其父節點
{
	for (int i = 0; i < g[x].size(); i++) // 列舉鄰居
	{
		int y = g[x][i];
		if (y == fa) continue; // 如果是父親,跳過
		dfs(y, x);
		f[x] |= f[y]; // 轉移
	}
}

int main()
{
	cin >> n >> k;
	for (int i = 1; i < n; i++)
	{
		int a, b;
		cin >> a >> b;
		g[a].push_back(b);
		g[b].push_back(a); // 建雙向邊
	}
	int root = 0;
	for (int i = 1; i <= k; i++)
	{
		int v;
		cin >> v;
		f[v] = 1; // 先把特殊點標為1
		if (!root) root = v; // 根節點
	}
	dfs(root, 0); // 呼叫
	for (int i = 1; i <= n; i++)
		cnt += f[i]; // 統計
	cout << cnt << endl;
	return 0;
} 

相關文章