P3225 [HNOI2012]矿场搭建

好不错的一道题,花了大量知识ac了

题目链接

https://www.luogu.com.cn/problem/P3225

图论的相关定义

https://www.cnblogs.com/poi-bolg-poi/p/12183166.html
可直接在这看↓

题解

起初做的时候以为,寻找每个割点,在割点形成的联通块中判断。
每有一个联通块,ans1 ++; 联通块size相乘 得到ans2。
后来一个点都没过,思路是错误的。

正解 :

上边方法的错误在ans1, ans1错了 所以ans2也错了。
首先我们可以知道,求割点是必要的。
对于割点形成的点双,有以下几种情况

  • 不存在割点 rt
  • 一个割点 rt

    已标黑割点
  • 多个割点 rt

之所以分为这三种情况, 是因为所产生的答案计数不同。

开始分类讨论
$ 1° $ 不存在割点。

当出口矿点坍塌的时候,必然有需要另一个矿点可以作为出口.

每个点双彼此之间由割点连接构成,不存在割点即此点双完全不可到其他点双,需要单独考虑。

也就是在当前点双中任选两个点作为出口,$ C(n,2) $种可选情况.

$ 2 ° $ 一个割点。

当割点所在矿点坍塌时,必然会使图不连通

当该割点塌陷的时候,图就不会联通,必然要在当前点双里在建一个出口

如 样例1

点双$ { $ $ 1,6,2,3,5 $ $ } $ $中,除去1点,皆符合情况。

$ 3° $ 多个割点。

因为存在多个割点,任意一个矿点坍塌都不会对图的连通性造成影响

当当前所在点双的某个割点塌陷后,图依然联通,必然可以走到其他的点双,从其他点双的出口出去,对答案无影响。
综上所述(雾) 。。。。

代码

挺重要的,还是要看看怎么写

#include<bits/stdc++.h>
#define clear(a) memset(a, 0, sizeof a)
using namespace std;
const int N = 5e2+50;
struct node{ int next, to; }edge[N<<1];
int cnt, head[N];
inline void add(int from, int to) { edge[++cnt] = (node) {head[from], to}, head[from] = cnt;}
int low[N], dfn[N], visited[N], cut[N];
int tot, sky, size, num;

void tarjan(int u, int f) {
	low[u] = dfn[u] = ++tot; int child = 0;
	for(int i = head[u]; i; i = edge[i].next) {
		int v = edge[i].to;
		if(!dfn[v]) {
			tarjan(v, f), low[u] = min(low[u], low[v]);
			if(u == f) child ++;
			if(u != f && low[v] >= dfn[u]) cut[u] = 1;
		}else low[u] = min(low[u], dfn[v]);
	}
	if(u == f && child >= 2) cut[u] = 1;
}
void dfs(int u) {
	size++, visited[u] = sky;
	for(int i = head[u]; i; i = edge[i].next) {
		int v = edge[i].to;
		if(visited[v] != sky && cut[v]) num ++, visited[v] = sky;
		if(!visited[v] && !cut[v]) dfs(v);
	}
}

int main() {
	int n = 0, m, t = 0;
	while(1) {
		clear(head), clear(edge), clear(cut);
		clear(visited), clear(low), clear(dfn);
		t++;
		n = cnt = tot = 0;
		scanf("%d", &m);
		if(m == 0) break;
		for(int i = 1; i <= m; i++) {
			int a, b; scanf("%d%d", &a, &b);
			add(a, b), add(b, a);
			n = max(max(a, b), n);
		}
		for(int i = 1; i <= n; i++) if(!dfn[i]) tarjan(i, i);
		long long ans1 = 0, ans2 = 1;
		for(int i = 1; i <= n; i++) {
			if(!visited[i] && !cut[i]) {
				num = size = 0, sky++,dfs(i);
				if(num == 0) ans1 += 2, ans2 *= size* (size - 1) / 2;
				if(num == 1) ans1 ++, ans2 *= size;
			}
		}
		printf("Case %d: %lld %lld\n", t, ans1, ans2);
	}
	return 0;
}
posted @ 2020-01-15 19:28  skkyk  阅读(156)  评论(0编辑  收藏  举报