2017多校北航F题 群论
cf链接:https://codeforces.com/gym/102253/problem/F
把a和b都看成函数,那么表达式转化为
\[f(i) = b(f(a(i)))
\]
显然f(i)可以由f(a(i))确定。表达式可以等价地转化为
\[f(a(i)) = b^{-1} * (f(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的环,那么答案就乘上
\[\prod_{j | siz}(cnt[j])
\]
因为取长度为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;
}