2017多校北航F题 群论

阅读量: searchstar 2019-07-07 14:48:50
Categories: Tags:

cf链接:https://codeforces.com/gym/102253/problem/F

把a和b都看成函数,那么表达式转化为

显然f(i)可以由f(a(i))确定。表达式可以等价地转化为

显然f(a(i))可以由f(i)确定。
本来的思路是扫一遍a数组,把i和a[i]用并查集放到同一个等价类中,然后每一个等价类都可以取出一个元素,随便赋上0~m-1的值,就可以把整个等价类中的取值确定,这样最终答案就是m^等价类个数,但是WA了。后来想到a可能形成环,所以有可能会出现矛盾的情况,要把矛盾的情况去掉。
可以证明a和b中的环是一个完整的环,不会出现6字型的环,因为不可能有两个不同的点指向同一个点。因此可以先预处理出b中所有的环的长度,统计出所有长度为i的环的节点的总个数cnt[i]。然后在a中找出所有的环,假如找到一个长度为siz的环,那么答案就乘上

因为取长度为siz的环中的某个节点,可以把它赋值为b中长度j整除siz的环中的任意一个节点,这样a绕一圈之后对应b绕 siz/j 圈,不会出现错位。

代码:

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

#define MAXN 100010
#define MAXM 100010

typedef long long LL;
typedef LL lint;

const lint p = 1000000007;

int main() {
	static int a[MAXN], b[MAXM];
	int n, m;

	static bool vis[MAXM];
	static int cnt[MAXM];	//cnt[i] is the number of points which forms a circle in b whose length is i

	int Ti = 0;
	while (~scanf("%d%d", &n, &m)) {
		int maxnm = max(n, m) + 1;
		memset(vis, 0, (m+1) * sizeof(vis[0]));
		memset(cnt, 0, maxnm * sizeof(cnt[0]));
		for (int i = 1; i <= n; ++i) {
			scanf("%d", a+i);
			++a[i];
		}
		for (int i = 1; i <= m; ++i) {
			scanf("%d", b+i);
			++b[i];
		}

		for (int i = 1; i <= m; ++i) {
			if (vis[i]) continue;
			vis[i] = true;
			int now = b[i], t = 1;
			while (now != i) {
				vis[now] = true;
				now = b[now];
				++t;
			}
			cnt[t] += t;
		}

		LL now_ans = 1;
		memset(vis, 0, (n+1) * sizeof(bool));
		for (int i = 1; i <= n; ++i) {
			if (vis[i]) continue;
			vis[i] = true;
			int t = 1, now = a[i];
			while (now != i) {
				vis[now] = true;
				now = a[now];
				++t;
			}
			int siz = t;
			lint points = 0;
			int j;
			for (j = 1; j * j < siz; ++j) {
				if (siz % j == 0) {
					points += cnt[j];
					points += cnt[siz / j];
				}
			}
			if (j * j == siz) {
				points += cnt[j];
			}
			(now_ans *= points) %= p;
		}

		printf("Case #%d: %d\n", ++Ti, (int)now_ans);
	}

	return 0;
}