洛谷 P3225 [HNOI2012]礦場搭建

題面

邊數N<=500的挖煤點,需要設置儘量少的逃生出口,使得某個煤礦倒塌後,其他挖煤點的工人仍然可以通過某條路徑到達逃生出口,並給出逃生出口最少設置數量與方案數
多組數據

分析

倒塌一座後…這一點令人想到割點(割點的倒塌會增加連通塊數量,產生隔斷),所以本題中顯然割點是比較重要的一種分析。

先用tarjan跑出割點,發現把出口安放到割點上不是最優的(割點的倒塌會使連通塊增加+出口減少,不利)
所以就要分析除割點外的其他部分,如何安放出口。

不難想到,先把割點刪去可以造成一系列無割聯通塊:

方案①:在每個這樣的連通塊內安放兩個出口。
看似最優,其實有反例

如這種情況,無割圖123是刪去cut1,cut2得到的連通塊,不難想到比方案1中更優的情形:無割圖內不放出口,無割圖2和3內各一個出口。
如果割點倒塌了一個這樣仍能保證逃生
如果一個出口倒塌,其內的人可以通過割點走到另一個出口。
所以方案①不是最優

這樣,就發現出口的安排和這種無割圖的割點連接數有關。
無割圖可以考慮成一種縮點,判斷每一個無割圖連接的割點數,不難發現:

方案②:
如果這個連通塊內本無割點,則只需要安排2個出口,爲保證即便一個倒塌,其他也能逃生,Cn2C_{n}^2種安排
如果一個無割圖連接着一個割點,其內只需安排一個出口,n種安排
無割圖連接2個及以上割點,不需安排出口

不難想到,已知割點後再一次dfs,處理出每一個塊的size及割點連接數,用組合數學即可計算出種數

代碼

#include "cstdlib"
#include <iostream>
#include <stdio.h>
#include <string.h>
#include<algorithm>
#include <string>
#include<vector>
using namespace std;
vector<int> G[510];//i爲頂點,G[i]爲從其延伸的邊集合
int dfs_clock = 0;//時間戳
int pre[510];//每個節點被遍歷到時的時間戳
int iscut[510];//記錄這個點是不是割點
int low[510];// low[u]表示dfs樹中u及其後代經過單線能連回的最早祖先的pre值
int dfs(int u, int fa)//處理u節點的bfs,其父爲fa
{
	int lowu = pre[u] = ++dfs_clock;//給u打上編號
	int child = 0;//u的孩子數量
	int v, lowv;//u的某個鄰居,這個鄰居的low值
	for (int i = 0; i < G[u].size(); i++)
	{
		v = G[u][i];
		if (!pre[v])//v未被訪問過,是子節點
		{
			child++;
			lowv = dfs(v, u);
			lowu = min(lowu, lowv);
			if (lowv >= pre[u])iscut[u] = 1;//u的子節點v中沒有能連到比u的clock序更前的邊,割點
		}
		else if (pre[v] < pre[u] && v != fa)//v已被訪問過,且不是u的父親鄰居(u連出的這條邊是反向邊)
		{
			lowu = min(lowu, pre[v]);
		}
	}
	if (fa == 0 && child == 1)iscut[u] = 0;//根節點且只有一個子,這個根不是割點,2+個子節點則是
	low[u] = lowu;
	return lowu;
}
int id[510];//刪去割點後,其餘元素屬於的連通塊編號
int siz[510];//除去割點後,剩餘連通塊的元素個數
int cutnum[510];//上面這種連通塊與多少割點相連
void chunk(int s,int num)//當前節點,當前節點所屬連通塊
{
	id[s] = num;
	siz[num]++;
	int cur;
	for (int i = 0; i < G[s].size(); i++)
	{
		cur = G[s][i];
		if (id[cur]!=num) {//這一次未被訪問過
			if (iscut[cur]) { cutnum[num]++; id[cur] = num; }
			else chunk(cur, num);
		}
	}
}
int max(int &a, int &b, int &c)
{
	if (a > b)
	{
		if (a > c)return a;
		else return c;
	}
	else
	{
		if (b > c)return b;
		else return c;
	}
}
int main()
{
	ios::sync_with_stdio(false);
	int n, m;//點數,邊數
	int x, y;
	for (int o = 1;;o++) {
		n = 0;
		cin >> m;
		if (m == 0)break;
		for (int i = 0; i < m; i++)
		{
			cin >> x >> y;
			n = max(x, y, n);
			G[x].push_back(y);
			G[y].push_back(x);
		}
		for (int i = 1; i <= n; i++)
		{
			if (!pre[i])dfs(i, 0);//有多個聯通區域
		}
		//iscut[u]==1表示是割點
		int cnt = 0;
		for (int i = 1; i <= n; i++)
		{
			if (iscut[i] == 0 && (!id[i])) { cnt++;chunk(i, cnt); }
		}
		long long ans = 1;
		int c = 0;//安放個數
		for (int i = 1; i <= cnt; i++)
		{
			if (cutnum[i] == 0) { ans *= siz[i] * (siz[i] - 1) / 2; c += 2; }//本身是個無割點連通塊
			else if (cutnum[i] == 1) { ans *= siz[i]; c++; }//連接着一個割點的無割圖
			else ans *= 1;//連接着>=2個割點的無割圖
		}

		cout << "Case " << o << ": " << c << " " << ans<<endl;

		for (int i = 1; i <= n; i++)G[i].clear();
		memset(low, 0, 510 * sizeof(int));
		memset(pre, 0, 510 * sizeof(int));
		memset(iscut, 0, 510 * sizeof(int));
		memset(id, 0, 510 * sizeof(int));
		memset(siz, 0, 510 * sizeof(int));
		memset(cutnum, 0, 510 * sizeof(int));
		dfs_clock = 0;
		//初始化過程
	}
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章