礦場搭建 題解

Peppa_Even_Pig發表於2024-03-16

題目描述

煤礦工地可以看成是由隧道連線挖煤點組成的無向圖。為安全起見,希望在工地發生事故時所有挖煤點的工人都能有一條出路逃到救援出口處。於是礦主決定在某些挖煤點設立救援出口,使得無論哪一個挖煤點坍塌之後,其他挖煤點的工人都有一條道路通向救援出口。

請寫一個程式,用來計算至少需要設定幾個救援出口,以及不同最少救援出口的設定方案總數。

輸入格式

image

輸出格式

輸入檔案中有多少組資料,輸出檔案中就有多少行。每行對應一組輸入資料的結果。

其中第 i 行以 Case i: 開始(注意大小寫,Case 與 i 之間有空格,i 與 : 之間無空格,: 之後有空格),其後是用空格隔開的兩個正整數,第一個正整數表示對於第 組輸入資料至少需要設定幾個救援出口,第二個正整數表示對於第 組輸入資料不同最少救援出口的設定方案總數。

輸出格式參照以下輸入輸出樣例。

樣例

樣例輸入

9
1 3
4 1
3 5
1 2
2 6
1 5
6 3
1 6
3 2
6
1 2
1 3
2 4
2 5
3 6
3 7
0

樣例輸出

Case 1: 2 4
Case 2: 4 1

image

題解

這是一張無向圖,我們可以發現,對於它的一個子圖D,如果D中任意兩點都聯通,那麼我們就可以將D縮成一個點,然後在進行操作,這不就是Tarjan求點雙連通分量嗎;

求完以後,很顯然,點雙的個數就是我們要找的最少出口個數(這裡不再過多進行解釋);

對於第二問,應該如何處理呢?

我們知道,每個D中可能會有0,1,2...個對於整張圖的割點(點雙性質),對於這幾種情況,我們可以分別討論;

  1. 當割點個數為0時,很顯然,整張圖是點雙,可以得出最少出口個數為2(因為當一個出口塌了的時候,還可以從另一個出去),方案數為m * (m - 1) >> 1(其中m為點數,從m個點中選兩個,用排列即可解決);

image

  1. 當割點個數為1時,我們只需在每個D中建一個不是割點的出口即可;

證明:當割點塌了時,可以從D中的出口出;當出口塌了時,可以從割點去其他的D中出

image

  1. 當割點個數>1時,不用設出口,因為從D中可以去至少2個其它的D出去;

image

上述圖片轉載自https://www.cnblogs.com/Charlieljk/p/17888945.html

如果還是看不懂,去搜搜Tarjan求點雙

程式碼

#include <iostream>
#include <cstdio>
#include <cstring>
#include <stack>
#include <vector>
#include <algorithm>
using namespace std;
int n, m;
struct sss{
	int t, ne;
}e[500005];
int h[500005], cnt;
void add(int u, int v) {
	e[++cnt].ne = h[u];
	h[u] = cnt;
	e[cnt].t = v;
}
int dfn[500005], low[500005];
int num;
stack<int> s;
vector<int> ds[500005];
vector<int> a;
bool cd[500005];
int dcnt;
void tarjan(int x, int root, int fa) { //Tarjan求點雙聯通分量;
	dfn[x] = low[x] = ++num;
	s.push(x);
	int son = 0;
	bool first = true;
	for (int i = h[x]; i; i = e[i].ne) {
		int u = e[i].t;
		if (first && u == fa) {
			first = false;
			continue;
		}
		if (!dfn[u]) {
			son++;
			tarjan(u, root, x);
			low[x] = min(low[x], low[u]);
			if (low[u] >= dfn[x]) {
				if (x != root || son > 1) {
					cd[x] = true;
				}
					dcnt++;
					ds[dcnt].push_back(x);
					int t = 0;
					do {
						t = s.top();
						s.pop();
						ds[dcnt].push_back(t);
					} while(t != u);
			}
		} else {
			low[x] = min(low[x], dfn[u]);
		}
	}
	return;
}
int main() {
	scanf("%d", &n);
	int ssum = 0;
	while(n) {
		int x, y;
		ssum++;
		memset(dfn, 0, sizeof(dfn));
		memset(low, 0, sizeof(low));
		cnt = 0;
		num = 0;
		dcnt = 0;
		long long ans = 0;
		long long an = 1;
		memset(h, 0, sizeof(h));
		memset(e, 0, sizeof(e));
		memset(cd, 0, sizeof(cd));
		memset(ds, 0, sizeof(ds));
		a.clear();
		while(!s.empty()) s.pop();
		for (int i = 1; i <= n; i++) {
			scanf("%d %d", &x, &y);
			add(x, y);
			add(y, x);
			a.push_back(x);
			a.push_back(y);
		}
		sort(a.begin(), a.end());
		m = unique(a.begin(), a.end()) - a.begin(); //m為點數;
		for (int i = 1; i <= m; i++) {
			if (!dfn[i]) tarjan(i, i, i);
		}
		for (int i = 1; i <= dcnt; i++) {
			int c = 0;
			for (int j = 0; j < ds[i].size(); j++) {
				if (cd[ds[i][j]]) c++;
			}
			if (c == 1) {
				ans++;
				an *= (ds[i].size() - 1);
			}
		}
		if (ds[1].size() == m) { //整張圖是點雙;
			ans = 2;
			an = ds[1].size() * (ds[1].size() - 1) >> 1;
		}
		printf("Case %d: %lld %lld\n", ssum, ans, an);
		scanf("%d", &n);
	}
	return 0;
}

相關文章