BZOJ 2730 矿场搭建 割点

試著忘記壹切 提交于 2020-02-14 18:16:25

一、内容

矿工地可以看成是由隧道连接挖煤点组成的无向图。为安全起见,希望在工地发生事故时所有挖煤点的工人都能有一条出路逃到救援出口处。于是矿主决定在某些挖煤点设立救援出口,使得无论哪一个挖煤点坍塌之后,其他挖煤点的工人都有一条道路通向救援出口。请写一个程序,用来计算至少需要设置几个救援出口,以及不同最少救援出口的设置方案总数。 

Input

输入文件有若干组数据,每组数据的第一行是一个正整数 N(N≤500),表示工地的隧道数,接下来的 N 行每行是用空格隔开的两个整数 S 和 T,表示挖       S 与挖煤点 T 由隧道直接连接。输入数据以 0 结尾。

Output

输入文件中有多少组数据,输出文件 output.txt 中就有多少行。每行对应一组输入数据的 结果。其中第 i 行以 Case i: 开始(注意大小写,Case 与 i 之间有空格,i 与:之间无空格,: 之后有空格),其后是用空格隔开的两个正整数,第一个正整数表示对于第 i 组输入数据至少需 要设置几个救援出口,第二个正整数表示对于第 i 组输入数据不同最少救援出口的设置方案总 数。输入数据保证答案小于 2^64。输出格式参照以下输入输出样例。

Sample Input

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 

Sample Output

Case 1: 2 4
Case 2: 4 1

二、思路

  • 首先给出的图可能分为多个连通块,ans1为出口数,ans2为方案数。只需要把不同连通块出口数相加,方案树相乘就可。
  • 从一个连通块出发:
    在这里插入图片描述

三、代码

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector> 
using namespace std;
typedef unsigned long long ull;
const int N = 1005, M = N * 2;
struct E {int v, next;} e[M];
int n, m, len, u, v, root, num, top, dcc_cnt, h[N], dfn[N], low[N], stack[N]; 
bool cut[N]; //是否是割点
vector<int> dcc[N]; //每个点连通分量里面的节点编号
void add(int u, int v) {e[++len].v = v; e[len].next = h[u]; h[u] = len;}
void tarjan(int u) {
	dfn[u] = low[u] = ++num;
	stack[++top] = u;
	//如果这个点是孤立点 
	if (u == root && h[u] == 0) dcc[++dcc_cnt].push_back(u);
	int cnt = 0; //满足条件的子节点数量 
	for (int j = h[u]; j; j = e[j].next) {
		int v = e[j].v;
		if (!dfn[v]) {
			tarjan(v);
			low[u] = min(low[u], low[v]);
			if (dfn[u] <= low[v]) {
				cnt++;
				if (root != u || cnt > 1) cut[u] = true;
				++dcc_cnt; int z;
				do {
					z = stack[top--];
					dcc[dcc_cnt].push_back(z); 
				} while (z != v); 
				dcc[dcc_cnt].push_back(u); //割点也要放入其中 
			}
		} else low[u] = min(low[u], dfn[v]); 
	} 
}
int main () { 
	int cas = 1;
	while (scanf("%d", &m), m) {
		for (int i = 1; i <= dcc_cnt; i++) dcc[i].clear(); 
		memset(h, 0, sizeof(h)); len = num = n = top = dcc_cnt = 0;
		memset(dfn, 0, sizeof(dfn));
		memset(cut, false, sizeof(cut));
		for (int i = 1; i <= m; i++) {
			scanf("%d%d", &u, &v);
			add(u, v); add(v, u);
			n = max(n, max(u, v));
		}
		ull ans1 = 0, ans2 = 1; //ans1是出口数量 ans2方案数 
		for (root = 1; root <= n; root++) if (!dfn[root]) tarjan(root);
		for (int u = 1; u <= dcc_cnt; u++) {
			int size = dcc[u].size(), cnt = 0; //cnt代表割点的数量 有多少个割点那么这dcc就有多少度 
			for (int j = 0; j < size; j++) {
				int v = dcc[u][j];
				if (cut[v]) cnt++;
			} 
			if (cnt == 0) ans1 += 2, ans2 *= size * (size - 1) / 2;
			else if (cnt == 1) ans1 += 1, ans2 *= size - 1; 
		} 
		printf("Case %d: %llu %llu\n", cas++, ans1, ans2);
	}
	return 0;
} 
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!