[BZOJ1064][Noi2008]假面舞会

时间:2021-07-08 00:34:35

[BZOJ1064][Noi2008]假面舞会

试题描述

一年一度的假面舞会又开始了,栋栋也兴致勃勃的参加了今年的舞会。今年的面具都是主办方特别定制的。每个参加舞会的人都可以在入场时选择一 个自己喜欢的面具。每个面具都有一个编号,主办方会把此编号告诉拿该面具的人。为了使舞会更有神秘感,主办方把面具分为k (k≥3)类,并使用特殊的技术将每个面具的编号标在了面具上,只有戴第i 类面具的人才能看到戴第i+1 类面具的人的编号,戴第k 类面具的人能看到戴第1 类面具的人的编号。 参加舞会的人并不知道有多少类面具,但是栋栋对此却特别好奇,他想自己算出有多少类面具,于是他开始在人群中收集信息。 栋栋收集的信息都是戴第几号面具的人看到了第几号面具的编号。如戴第2号面具的人看到了第5 号面具的编号。栋栋自己也会看到一些编号,他也会根据自己的面具编号把信息补充进去。由于并不是每个人都能记住自己所看到的全部编号,因此,栋栋收集的信 息不能保证其完整性。现在请你计算,按照栋栋目前得到的信息,至多和至少有多少类面具。由于主办方已经声明了k≥3,所以你必须将这条信息也考虑进去。

输入

第一行包含两个整数n, m,用一个空格分隔,n 表示主办方总共准备了多少个面具,m 表示栋栋收集了多少条信息。接下来m 行,每行为两个用空格分开的整数a, b,表示戴第a 号面具的人看到了第b 号面具的编号。相同的数对a, b 在输入文件中可能出现多次。

输出

包含两个数,第一个数为最大可能的面具类数,第二个数为最小可能的面具类数。如果无法将所有的面具分为至少3 类,使得这些信息都满足,则认为栋栋收集的信息有错误,输出两个-1。

输入示例1

6 5
1 2
2 3
3 4
4 1
3 5

输出示例1

4 4

输入示例2

3 3
1 2
2 1
2 3

输出示例2

-1 -1

数据规模及约定

100%的数据,满足n ≤ 100000, m ≤ 1000000。

题解

先看看我们知道什么。假设一个合法的答案是 k,则给了一条有向边(a, b)后,就说明 a 在第 i 组且 b 在第 i + 1 组,或者是 a 在第 k 组且 b 在第 1 组。

不难想到当给定的有向边形成一个环之后,k 一定是这个环长度的约数(因为它可以从第 1 组某个节点连到第 2 组某个节点,一直连到第 k 组某个节点,再从第 k 组的那个节点直接连一条边到第 1 组的某个节点,如此往复最终首尾相接,则这个环绕了若干圈这 k 个组)。

还有一种情况:a -> b -> c -> d -> e 且 a -> e,我们可以尝试给它分组,先考虑前面那条链,可以暂时将它们分成这个样子:a∈1,b∈2,c∈3,d∈4,e∈5,那么再加上 a -> e 这条边后发现 e 的编号应该等于 a 的编号加 1,所以将其与 b 分到第 2 组,同理 a 的编号应该等于原来 e 的编号减 1,所以将其与 d 分到第 4 组,最终结果是:b, e∈2,c∈3,a, d∈4,最终组数为 3。

综上两点可以得出一种做法:从每个连通分量的一个节点开始标号,顺着有向边走时标号加 1,逆着走时减 1,发现当前结点有标号时,则最大答案就是所有这种情况中的“原标号与新标号之差的绝对值”取最大公约数,最小答案就是所有所有这样的值取大于等于 3 的最小公约数。

注意一个特判,当给的图在以上两种情况之外,即将有向边变成无向边后没有圈的图(成为一个森林)时,每个连通分量的最大标号 - 最小标号 + 1,求和就是最大答案,最小答案是 3(若合法).

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <stack>
#include <vector>
#include <queue>
#include <cstring>
#include <string>
#include <map>
#include <set>
using namespace std;

const int BufferSize = 1 << 16;
char buffer[BufferSize], *Head, *Tail;
inline char Getchar() {
	if(Head == Tail) {
		int l = fread(buffer, 1, BufferSize, stdin);
		Tail = (Head = buffer) + l;
	}
	return *Head++;
}
int read() {
	int x = 0, f = 1; char c = Getchar();
	while(!isdigit(c)){ if(c == '-') f = -1; c = Getchar(); }
	while(isdigit(c)){ x = x * 10 + c - '0'; c = Getchar(); }
	return x * f;
}

#define maxn 100010
#define maxm 2000010
#define oo 2147483647
int n, m, head[maxn], next[maxm], to[maxm], dist[maxm], ans;

void AddEdge(int a, int b) {
	to[++m] = b; dist[m] = 1; next[m] = head[a]; head[a] = m;
	swap(a, b);
	to[++m] = b; dist[m] = -1; next[m] = head[a]; head[a] = m;
	return ;
}

int gcd(int a, int b) { return !b ? a : gcd(b, a % b); }

int mark[maxn], mx, mn;
bool vis[maxn];
void dfs(int u, int x) {
	if(vis[u]){ ans = gcd(ans, abs(mark[u] - x)); return ; }
	vis[u] = 1; mark[u] = x; mx = max(mx, x); mn = min(mn, x);
	for(int e = head[u]; e; e = next[e]) dfs(to[e], x + dist[e]);
	return ;
}

int main() {
	n = read(); int m = read();
	for(int i = 1; i <= m; i++) {
		int a = read(), b = read();
		AddEdge(a, b);
	}
	
	int tmp = 0;
	for(int i = 1; i <= n; i++) if(!vis[i]) {
		mx = -oo; mn = oo;
		dfs(i, 1);
		tmp += mx - mn + 1;
	}
	int a2;
	if(!ans) ans = tmp, a2 = 3;
	else for(a2 = 3; a2 <= ans && ans % a2; a2++) ;
	if(ans < 3) puts("-1 -1");
	else printf("%d %d\n", ans, a2);
	
	return 0;
}