【图的遍历】【NOI2008】假面舞会

【问题描述】
一年一度的假面舞会又开始了,栋栋也兴致勃勃的参加了今年的舞会。
今年的面具都是主办方特别定制的。
每个参加舞会的人都可以在入场时选择一个自己喜欢的面具。每个面具都有一个编号,主办方会把此编号告诉拿该面具的人。
为了使舞会更有神秘感,主办方把面具分为 k (k≥3)类,并使用特殊的技术将每个面具的编号标在了面具上,只有戴第 i 类面具的人才能看到戴第 i+1 类面具的人的编号,戴第 k 类面具的人能看到戴第 1 类面具的人的编号。
参加舞会的人并不知道有多少类面具,但是栋栋对此却特别好奇,他想自己算出有多少类面具,于是他开始在人群中收集信息。栋栋收集的信息都是戴第几号面具的人看到了第几号面具的编号。如戴第 2号面具的人看到了第 5 号面具的编号。栋栋自己也会看到一些编号,他也会根据自己的面具编号把信息补充进去。
由于并不是每个人都能记住自己所看到的全部编号,因此,栋栋收集的信息不能保证其完整性。现在请你计算,按照栋栋目前得到的信息,至多和至少有多少类面具。由于主办方已经声明了 k≥3,所以你必须将这条信息也考虑进去。
【输入格式】
输入文件 party.in 第一行包含两个整数 n, m,用一个空格分隔,n 表示主办方总共准备了多少个面具,m 表示栋栋收集了多少条信息。
接下来 m 行,每行为两个用空格分开的整数 a, b,表示戴第 a 号面具的人看到了第 b 号面具的编号。相同的数对 a, b 在输入文件中可能出现多次。
【输出格式】
输出文件 party.out 包含两个数,第一个数为最大可能的面具类数,第二个数为最小可能的面具类数。如果无法将所有的面具分为至少 3 类,使得这些信息都满足,则认为栋栋收集的信息有错误,输出两个-1。
【输入样例一】
6 5
1 2
2 3
3 4
4 1
3 5
【输出样例一】
4 4
【输入样例二】
3 3
1 2
2 1
2 3
【输出样例二】
-1 -1
【数据规模和约定】
50%的数据,满足 n ≤ 300, m ≤ 1000;
100%的数据,满足 n ≤ 100000, m ≤ 1000000。
从题目描述中不难抽象出一个有向图——并且可以发现它可能是有环的。
由于每次必须将一个连通分量全部处理掉,所以双向建边,正向边权值为1,反向边权值为-1。
对图进行遍历染色(遇到正向边颜色加一,遇到反向边颜色减一),若冲突,那么该环的最大周期为该点已经被染成的颜色与期望被染成的颜色之差。

若不存在环,那么最大答案为所有连通分量(此时为一个树形图)的最大长度(最大标号和最小标号之差)之和,最小答案为3。(当然若最大值都小于3那么答案不存在)。
若存在环,那么最大答案为所有环的最大周期的最大公约数,最小答案为大于等于3的一个最小公约数。

/****************************\
 * @prob: NOI2008 party     *
 * @auth: Wang Junji        *
 * @stat: Accepted.         *
 * @date: May. 18th, 2012   *
 * @memo: 图的遍历           *
\****************************/
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <string>
#include <cstring>

const int maxN = 200010, INF = 0x3f3f3f3f;
struct Edge
{
	int v, d; Edge *next; Edge() {}
	Edge(int v, int d, Edge *next): v(v), d(d), next(next) {}
} *edge[maxN]; bool marked[maxN];
int Belong[maxN], col[maxN], cyc[maxN];
int Ccnt, Pcnt, n, m, ans_min = 3, ans_max;

inline int getint()
{
	int res = 0; char tmp;
	while (!isdigit(tmp = getchar()));
	do res = (res << 3) + (res << 1) + tmp - '0';
	while (isdigit(tmp = getchar()));
	return res;
}

void Dfs(int u, int c)
{
	col[u] = c; marked[u] = 1; Belong[u] = Pcnt;
	for (Edge *p = edge[u]; p; p = p -> next)
	if (!marked[p -> v]) Dfs(p -> v, c + p -> d);
	else if (c + p -> d - col[p -> v])
		cyc[++Ccnt] = std::abs(c + p -> d - col[p -> v]);
	return;
} //找出连通分量,并求出各个环的最大周期(若有环存在)。

inline int gcd(int a, int b)
{for (int tmp; b; a = b, b = tmp) tmp = a % b; return a;}

inline void work1()
{
	ans_max = cyc[1];
	for (int i = 1; i < Ccnt + 1; ++i)
		ans_max = gcd(ans_max, cyc[i]);
	if (ans_max < 3) {ans_min = ans_max = -1; return;}
	for (; ans_min < ans_max + 1; ++ans_min)
	{
		bool flag = 1;
		for (int i = 1; i < Ccnt + 1; ++i)
			if (cyc[i] % ans_min) {flag = 0; break;}
		if (flag) break;
	}
	return;
} //There exists circles.

inline void work2()
{
	static int min_col[maxN], max_col[maxN];
	memset(min_col, 0x3f, sizeof min_col);
	for (int i = 1; i < n + 1; ++i)
		min_col[Belong[i]] = std::min(min_col[Belong[i]], col[i]),
		max_col[Belong[i]] = std::max(max_col[Belong[i]], col[i]);
	for (int i = 1; i < Pcnt + 1; ++i)
		ans_max += max_col[i] - min_col[i] + 1;
	if (ans_max < 3) ans_min = ans_max = -1;
	return;
} //There exists no circle.

int main()
{
	freopen("party.in", "r", stdin);
	freopen("party.out", "w", stdout);
	n = getint(); m = getint();
	while (m--)
	{
		int u = getint(), v = getint();
		edge[u] = new Edge(v, 1, edge[u]);
		edge[v] = new Edge(u, -1, edge[v]);
	}
	for (int i = 1; i < n + 1; ++i)
		if (!marked[i]) ++Pcnt, Dfs(i, 1);
	Ccnt ? work1() : work2();
	printf("%d %d\n", ans_max, ans_min); return 0;
}

你可能感兴趣的:(c,Date)