【HDU 3844】Mining Your Own Business(点双连通分量,割点)

目录

  • 题目
      • Problem Description
      • Input
      • Output
      • Sample Input
      • Sample Output
      • Source
  • 思路
      • 割点
        • 定义
        • 求法
  • 代码

题目

Problem Description

John Digger is the owner of a large illudium phosdex mine. The mine is made up of a series of tunnels that meet at various large junctions. Unlike some owners, Digger actually cares about the welfare of his workers and has a concern about the layout of the mine. Specifically, he worries that there may a junction which, in case of collapse, will cut off workers in one section of the mine from other workers (illudium phosdex, as you know, is highly unstable). To counter this, he wants to install special escape shafts from the junctions to the surface. He could install one escape shaft at each junction, but Digger doesn’t care about his workers that much. Instead, he wants to install the minimum number of escape shafts so that if any of the junctions collapses, all the workers who survive the junction collapse will have a path to the surface.

Write a program to calculate the minimum number of escape shafts and the total number of ways in which this minimum number of escape shafts can be installed.

Input

The input consists of several test cases. The first line of each case contains a positive integer N ( N < = 5 × 1 0 4 ) N (N <= 5×10^4) N(N<=5×104) indicating the number of mine tunnels. Following this are N lines each containing two distinct integers s and t, where s and t are junction numbers. Junctions are numbered consecutively starting at 1. Each pair of junctions is joined by at most a single tunnel. Each set of mine tunnels forms one connected unit (that is, you can get from any one junction to any other).

The last test case is followed by a line containing a single zero.

Output

For each test case, display its case number followed by the minimum number of escape shafts needed for the system of mine tunnels and the total number of ways these escape shafts can be installed. You may assume that the result fits in a signed 64-bit integer.

Follow the format of the sample output.

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

Source

2011 World Final

思路

失踪人口回归

十分经(jian)典(nan)的一题。
题目描述的长度感人……
那这题什么意思呢?
聪明机智 的我决定 盗一幅图
在这里插入图片描述
咳咳。
那这题该怎么做呢?
这时候,我们应该要知道一个东西:
——割点


割点

定义

很简单,一个图,去掉一点,图就不通了,这就叫割点。

求法

1 、 D F S 1、DFS 1DFS
依次删除每一个点,然后 D F S DFS DFS ,。
(像我一样的蒟蒻的第一想法)

2 、 T a r j a n 2、Tarjan 2Tarjan
听说过 t a r j a n tarjan tarjan 这个东西吗。
可不是求强连通分量的那个哦。
当然,和那个算法类似。
简单说一下就是这样:

  • 如果一个根有超过1棵子树,那么它是一个割点。
  • 如果一个非叶节点的某个儿子没有回边能到达高于它的点,那么它是一个割点。
  • 叶节点不是割点。

那么具体是这个亚子的:

  • 首先,我们要选定一个根节点,从该根节点开始遍历整个图(使用 D F S DFS DFS
  • 首先我们需要两个数组: d f n dfn dfn l o w low low d f n dfn dfn 表示顶点第几个被首次访问, l o w low low 表示顶点及其子树中的点,通过非父子边,能够回溯到的最早的点的 d f n dfn dfn 值。对于边 ( u , v ) (u, v) (u,v),如果 l o w ≥ d f n low≥dfn lowdfn,此时 u u u 就是割点。
  • 那么怎么来求 l o w low low 数组呢? 对于一条边 ( u , v ) (u,v) (u,v) ,如果 v v v 没有被访问过那么继续访问 v v v 节点,此时 l o w [ u ] = m i n ( l o w [ v ] , l o w [ u ] ) low[u]=min(low[v],low[u]) low[u]=min(low[v],low[u]) ,如果 v v v 已被访问过,那么就可以不必继续 D F S DFS DFS,直接 l o w [ u ] = m i n ( l o w [ u ] , d f n [ v ] ) low[u]=min(low[u],dfn[v]) low[u]=min(low[u],dfn[v])

一道模板题
(主要是洛谷里面的题解讲得更好)

另: t a r j a n tarjan tarjan 读作 塔扬 ……


那么这题大概就是这个亚子的:

  • 如果这个图没有割点(即点双联通, b c c bcc bcc),那么一切完美,随便选两个点就行了。
  • 如果有1个割点,则必须选择分量内除割点之外的任意一个点。
  • 如果有多个(2个及以上)割点,则这个分量不需要涂色。

BCC其实就很好求了,在 t a r j a n tarjan tarjan求割点的基础上记点东西。

具体见代码↓↓↓

代码

(由于HDU崩了,暂时不知道题目正误)
(大概率会跳进出题人的坑里,虽然调了一个晚上……)

#include 

using namespace std;
int n, m, tot, dfn[101010], low[101010], lev, cnt, tp;
int head[101010];
bool b[101010], v[101010];
struct node {
	int y, nxt;
} e[101010];
vector <int> bcc[101010];
int bccno[101010], sx[101010], sy[101010];

void addedge(int x, int y) {
	e[++tot].y = y;
	e[tot].nxt = head[x];
	head[x] = tot;
}

void tarjan(int x, int pre) {
	dfn[x] = low[x] = ++lev;
	int num = 0;
	for (int i = head[x]; i; i = e[i].nxt) {
		int y = e[i].y;
		if (v[y]) { if (y != pre) low[x] = min(low[x], dfn[y]); }
		else {
			v[y] = 1; num++;
			sx[++tp] = x; sy[tp] = y;
			tarjan(y, x);
			if (dfn[x] <= low[y]) {
				if (pre != 0) b[x] = 1;
				bcc[++cnt].clear();
				while (!(sx[tp] == x && sy[tp] == y)) {
                    if (bccno[sy[tp]] != cnt)
						bccno[sy[tp]] = cnt, bcc[cnt].push_back(sy[tp]);
                    if (bccno[sx[tp]] != cnt)
						bccno[sx[tp]] = cnt, bcc[cnt].push_back(sx[tp]);
                    tp--;
                }
                if (bccno[sy[tp]] != cnt) bccno[sy[tp]] = cnt, bcc[cnt].push_back(sy[tp]);
                if (bccno[sx[tp]] != cnt) bccno[sx[tp]] = cnt, bcc[cnt].push_back(sx[tp]);
                tp--;
            }
            low[x] = min(low[x], low[y]);
		}
	}
    if (pre == 0 && num > 1) b[x] = 1;
	return ;
}

int main() {
	int ret = 0;
	while (~scanf("%d", &m)) {
		if (m == 0) return 0;
		tot = n = cnt = 0;
		memset(head, 0, sizeof head);
		memset(b, 0, sizeof b);
		memset(v, 0, sizeof v);
		memset(bccno, 0, sizeof bccno);
		for (int i = 1; i <= m; i++) {
			int x, y; scanf("%d%d", &x, &y);
			addedge(x, y), addedge(y, x);
			n = max(max(x, y), n);
		}
		for (int i = 1; i <= n; i++)
			if (v[i] == 0) lev = tp = 0, v[i] = 1, tarjan(i, 0);
		long long ans = 0, res = 1;
		for (int i = 1; i <= cnt; i++) {
			int num = 0;
			for (int j = 0; j < bcc[i].size(); j++)
				if (b[bcc[i][j]] == 1) num++;
			if (num == 1) ans++, res *= (bcc[i].size()-1);
		}
		if (cnt == 1) printf("Case %d:2 %lld\n", ++ret, (long long)(n*(n-1)) >> 1);
		else printf("Case %d:%lld %lld\n", ++ret, ans, res);
	}
	return 0;
}

你可能感兴趣的:(#,割点,图论,#,连通分量)