矿场搭建


三倍经验

P3225 [HNOI2012]矿场搭建

UVA1108 Mining Your Own Business

SP16185 BUSINESS - Mining your own business

题目大意

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

数据范围:
\(n \leq 500\),输入数据保证答案小于 \(2^{64}\)

解题思路

显然,先 Tarjan 跑出割点,然后 DFS 搜索所有的联通块(也可以直接跑点双,不过我考场时是这样写的)。

计算每一个联通块中的割点数目。

然后再分类讨论:

  • 如果没有割点,至少需要建立两个出口,从任意地方选择两个点建立
    ,如果设连通块的大小为 \(siz\),则对答案的贡献为 \(\frac{(siz-1)*siz}{2}\)
    • 如果轰炸其中一个出口,则其他点都可以从另一个出口逃走。
    • 如果轰炸的是其他点,不啥事都没有吗~
  • 如果这个连通块只有一个割点,只需要在连通块内设立一个出口,可以设立在任意一个非割点的地方,如果设连通块的大小为 \(siz\),则贡献为 \(siz-1\)(因为不能建立在割点上)。
    • 如果轰炸的是割点,则连通块内的其他点都可以通过刚刚建立的出口出去。
    • 如果轰炸的是我们刚刚建立的出口,那由于我们刚刚建立的出口不是割点,所以连通块内除了轰炸了的点,都可以从割点通向另一个连通块内的出口。
  • 如果这个连通块有两个及以上个割点,则不需要建立出口。
    • 如果轰炸的是其中一个割点,则可以通过其他割点到达其他的连通块。
    • 如果轰炸的是其他点,则轰炸的不是割点,啥事都没有~

最后将所有连通块的贡献乘起来(简单的乘法原理)。

如果有两个及以上个割点,则无需建立,可以直接到达其他联通块

AC CODE

边双联通分量

#include <bits/stdc++.h>
using namespace std;

#define _ 905

long long Case = 1;
long long n, m, Ans1, Ans2;

long long Cut, cnt_node, cntn, root;

int dfn[_], vis[_], low[_], siz[_];

stack<int> s;

bool cut[_];

int head[_], cnt;

struct Node
{
	int v, next;
} e[_ * _];

vector<int> cutt[_];

void add(int u, int v)
{
	e[++cnt] = (Node)
	{
		v, head[u]
	};
	head[u] = cnt;
}

void Tarjan(int u, int fa)
{
	dfn[u] = low[u] = ++cnt_node;
	s.push(u);
	int flag = 0;
	if(u == root && !head[u])
	{
		cutt[++cntn].push_back(u);
		return;
	}
	for (int i = head[u]; i; i = e[i].next)
	{
		int v = e[i].v;
		if (!dfn[v])
		{
			Tarjan(v, u);
			low[u] = min(low[u], low[v]);
			if (low[v] >= dfn[u])
			{
				flag++;
				if(u != root || flag > 1) cut[u] = 1;
				cntn++;
				while(1)
				{
					int now = s.top();
					s.pop();
					cutt[cntn].push_back(now);
					if(now == v) break;
				}
				cutt[cntn].push_back(u);
			}
		}
		else low[u] = min(low[u], dfn[v]);
	}
}

void init()
{
	memset(head, 0, sizeof(head));
	memset(dfn, 0, sizeof(dfn));
	memset(low, 0, sizeof(low));
	memset(cut, 0, sizeof(cut));
	memset(vis, 0, sizeof(vis));
	for(int i = 0; i < _; ++i) cutt[i].clear();
	cnt_node = 0;
	cnt = 0;
	n = 0;
	Ans1 = 0;
	Ans2 = 1;
	cntn = 0;
	while(!s.empty()) s.pop();
}

signed main()
{
	while (cin >> m && m)
	{
		init();

		for (long long i = 1; i <= m; ++i)
		{
			long long u, v;
			scanf("%lld%lld", &u, &v);
			add(u, v);
			add(v, u);
			n = max(n, max(u, v));
		}

		for (int i = 1; i <= n; ++i)
			if (!dfn[i]) Tarjan(root = i, i);

		int opt = 0;

		for (int i = 1; i <= cntn; ++i)
		{
			Cut = 0;
			for(int j = 0; j < cutt[i].size(); ++j)
				if(cut[cutt[i][j]]) Cut++;
			
			if (Cut == 0)
			{
				Ans1 += 2;
				Ans2 *= (long long)(cutt[i].size() - 1) * cutt[i].size() / 2;
			}
			
			if (Cut == 1)
			{
				Ans1++;
				Ans2 *= (long long)cutt[i].size() - 1;
			}
		}

		cout << "Case " << Case++ << ": " << Ans1 << " " << Ans2 << endl;
	}
	return 0;
}

割点 \(+\) dfs

#include <bits/stdc++.h>
using namespace std;

#define _ 905

long long Case = 1;
long long n, m, Ans1, Ans2;

long long num, Cut, cnt_node, root;

int dfn[_], vis[_], low[_];

bool cut[_];

int head[_], cnt;
struct Node
{
	int v, next;
} e[_ * _];

void add(int u, int v)
{
	e[++cnt] = (Node){v, head[u]};
	head[u] = cnt;
}

void Tarjan(int u, int fa)
{
	dfn[u] = low[u] = ++cnt_node;
	int flag = 0;
	for (int i = head[u]; i; i = e[i].next)
	{
		int v = e[i].v;
		if (!dfn[v])
		{
			Tarjan(v, u);
			low[u] = min(low[u], low[v]);
			if (low[v] >= dfn[u])
			{
				flag++;
				if(u != root || flag > 1) cut[u] = 1;
			}
		}
		else if (v != fa)
			low[u] = min(low[u], dfn[v]);
	}
}

void dfs(int u, int id)
{
	vis[u] = id;
	num++;
	for (int i = head[u]; i; i = e[i].next)
	{
		int v = e[i].v;
		if (cut[v] && vis[v] != id)
		{
			Cut++;
			vis[v] = id;
		}
		if (vis[v]) continue;
		dfs(v, id);
	}
}

void init()
{
	memset(head, 0, sizeof(head));
	memset(dfn, 0, sizeof(dfn));
	memset(low, 0, sizeof(low));
	memset(cut, 0, sizeof(cut));
	memset(vis, 0, sizeof(vis));
	cnt_node = 0;
	cnt = 0;
	n = 0;
	Ans1 = 0;
	Ans2 = 1;
}

signed main()
{
	while (cin >> m && m)
	{
		init();

		for (long long i = 1; i <= m; ++i)
		{
			long long u, v;
			scanf("%lld%lld", &u, &v);
			add(u, v);
			add(v, u);
			n = max(n, max(u, v));
		}

		for (int i = 1; i <= n; ++i)
			if (!dfn[i]) Tarjan(root = i, i);

		int opt = 0;
		
		for (int i = 1; i <= n; ++i)
		{
			if (!vis[i] && !cut[i])
			{
				num = 0;
				Cut = 0;
				dfs(i, ++opt);
				if (Cut == 0)
				{
					Ans1 += 2;
					Ans2 *= (long long)(num - 1) * num / 2;
				}
				if (Cut == 1)
				{
					Ans1++;
					Ans2 *= (long long)num;
				}
			}
		}
		cout << "Case " << Case++ << ": " << Ans1 << " " << Ans2 << endl;
	}
	return 0;
}


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM