【BZOJ1478】Sgu282 Isomorphism

本文介绍了如何利用Burnside引理或Polya定理解决图的同构问题,具体到BZOJ1478题目,通过计算点置换中边的染色方案等价类数量,然后除以点的数量,来得到最终答案。文章提到,可以将点的置换看作由若干环组成的无向图,并枚举这些环的长度(即整数拆分),进一步计算出边的染色方案等价类个数。时间复杂度与整数拆分相关。最后,文章给出了算法实现的思路。

【题目链接】

【思路要点】

  • 考虑Burnside引理或Polya定理,求解总共\(N!\)种点置换中边的染色方案等价类的个数总和,再将答案除以N!。
  • 考虑一个点集的置换\(P_{i}\),若将一个置换看做一张由\(N\)个点、\(N\)条边的无向图,那么显然,该图由若干个环组成。记其中每一个环长为\(A_{i}\),\(A\)可以看做一个\(N\)的拆分。
  • 53的拆分数为329931,枚举是可以接受的。
  • 不难发现,一个置换中边的染色方案等价类的个数只与上述拆分\(A\)有关。
  • 枚举N的整数拆分,分别计算拆分出现的次数\(tmp\)与置换中边的染色方案等价类的个数\(now\),将答案\(Ans\)加上\(tmp*M^{now}\)。
  • 考虑由点集的置换推边集的置换,\(now\)应当为\(A_{i}\)两两的\(Gcd\)之与\([\frac{A_{i}}{2}]\)的和。
  • \(tmp\)可以运用一定的组合数学的计算得出。
  • 时间复杂度\(O(∑^{N}_{i=1}i^{2}*S(N,i))\)其中\(S(N,i)\)表示整数N长度为i的拆分个数。

【代码】

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 65;
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
long long a[MAXN], b[MAXN][MAXN];
long long fac[MAXN], inv[MAXN];
long long P, n, k, ans;
long long power(long long a, long long b) {
	if (b == 0) return 1;
	long long tmp = power(a, b / 2);
	if (b % 2 == 0) return tmp * tmp % P;
	else return tmp * tmp % P * a % P;
}
long long gcd(long long a, long long b) {
	if (b == 0) return a;
	else return gcd(b, a % b);
}
long long getc(int x, int y) {
	return fac[x] * inv[y] % P * inv[x - y] % P;
}
void check(int depth) {
	a[depth + 1] = 0;
	long long now = 0;
	for (int i = 1; i <= depth; i++) {
		now += a[i] / 2;
		for (int j = i + 1; j <= depth; j++)
			now += b[a[i]][a[j]];
	}
	int lft = n, last = 0, tmp = 1;
	for (int i = 1; i <= depth; i++) {
		tmp = tmp * fac[a[i] - 1] % P; 
		tmp = tmp * getc(lft, a[i]) % P;
		lft -= a[i];
		if (a[i] != a[i + 1]) {
			tmp = tmp * inv[i - last] % P;
			last = i;
		}
	}
	ans = (ans + tmp * power(2, now) % P) % P;
}
void work(int depth, int sum, int now) {
	if (sum == 0) {
		check(depth - 1);
		return;
	}
	if (now == 0) return;
	work(depth, sum, now - 1);
	if (sum >= now) {
		a[depth] = now;
		work(depth + 1, sum - now, now);
	}
}
int main() {
	read(n), read(k), read(P);
	for (int i = 1; i <= n; i++)
	for (int j = 1; j <= n; j++)
		b[i][j] = gcd(i, j);
	fac[0] = inv[0] = 1;
	for (int i = 1; i <= n; i++) {
		fac[i] = fac[i - 1] * i % P;
		inv[i] = power(fac[i], P - 2);
	}
	work(1, n, n);
	ans = ans * inv[n] % P;
	printf("%lld\n", ans);
	return 0;
}


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值