P4708 画画 (Burnside)(组合数学)

传送门

考虑用 b u r n s i d e burnside 计数,枚举 n ! n! 种置换,求出每种置换下的不动点个数

先考虑该题的一种弱化版,就是无标号无向图个数

我们要将点的置换作用到边上

将置换画成很多个环,发现一条边要么在一个置换内,要么在不同的置换中

在一个置换内和在不同置换中的不动点个数分别求

在一个置换内的不动点个数是 n 2 \frac{n}{2} ,考虑如果有边 ( x , x + t ) (x,x+t) ,那么必有边 ( x + 1 , x + t + 1 ) . . . (x+1,x+t+1)... 以此类推,并且考虑 t t n t n-t 其实是等价的

在不同置换中,考虑两个置换的大小为 A , B A,B ,那么一条边会引申出 l c m ( A , B ) lcm(A,B) 条边,于是不动点个数其实是 g c d ( A , B ) gcd(A,B)

而这些等价类可以出现可以不出现,对答案的贡献是 2 c n t 2^{cnt}

发现只与置换的大小有关,暴力枚举拆分,算每个拆分的方案数

先有一个排列的方案数 n ! n! ,一个循环的贡献是 1 l e n \frac{1}{len} ,大小相同的循环之间无序,有一个 1 c n t [ i ] ! \frac{1}{cnt[i]!} 的贡献

考虑欧拉回路这个更强的限制

即每个点的度数为偶数

同样考虑一个置换内的边
若长度为奇数 2 l + 1 2*l+1 ,所有置换出不出现对点都是偶数的影响,可以不管
若长度为偶数 2 l 2*l ,其中 l 1 l-1 个不动点对奇偶性是没有影响的,而最后一个取所有对角线的不动点会使所有点度数 + 1

考虑一个置换间的边
对一个 A A 中的点度数的影响是 B g c d ( A , B ) \frac{B}{gcd(A,B)} ,对 B B 中的影响是 A g c d ( a , b ) \frac{A}{gcd(a,b)}

我们发现现在可以先将贡献乘上没有影响的 2 c n t 2^{cnt}

然后可以有 d i d_i 中方法使第 i 个置换的度数全部 + 1, e u , v e_{u,v} 中方法使 u , v u,v 置换的度数都 + 1

然后就是求使得满足限制的方案数,可以高斯消元异或矩阵,贡献为最后的自由元个数

但还有更妙的性质

我们对现在 e u , v e_{u,v} 形成的连通块,随便求一棵生成树,生成树外的边随便选,除根以外的点随便选

发现根和树边对应着唯一的可行解

点数为 n n ,点的选择数为 s s ,边的选择数为 m m ,的一个连通块

方案数就是 2 m ( n 1 ) + m a x ( 0 , ( s 1 ) ) 2^{m-(n-1)+max(0,(s-1))}

用并查集维护即可

主要就是求不动点个数,考虑在一个置换内或置换间的不动点,暴力枚举拆分数乘上方案数
通过一个生成树的构造快速求得可行解个数,挺巧妙的

#include<bits/stdc++.h>
#define cs const
using namespace std;
cs int Mod = 998244353;
int add(int a, int b){ return a + b >= Mod ? a + b - Mod : a + b; }
int mul(int a, int b){ return 1ll * a * b % Mod; }
int dec(int a, int b){ return a - b < 0 ? a - b + Mod : a - b; }
int ksm(int a, int b){ int ans = 1; for(;b;b>>=1,a=mul(a,a)) if(b&1) ans = mul(ans, a); return ans; }
void Add(int &a, int b){ a = add(a, b); }
cs int N = 55;
int gcd(int a, int b){ return !b ? a : gcd(b, a % b); }
int n, ans, inv[N], g[N][N];
int fa[N], sm[N]; 
int find(int x){ return x == fa[x] ? x : fa[x] = find(fa[x]); }
int loop[N], ct[N], now;
int calc(){
	int res = 0;
	for(int i = 1; i <= now; i++) fa[i] = i, sm[i] = 0;
	for(int i = 1; i <= now; i++){
		int v = loop[i]; res += ((v - 1) >> 1);
		if(!(v&1)) sm[i] = 1;
	}
	for(int i = 1; i <= now; i++){
		for(int j = i+1; j <= now; j++){
			int u = loop[i], v = loop[j], gc = g[u][v];
			int a = (u/gc) & 1, b = (v/gc) & 1;
			if(a && b) fa[find(i)] = find(j), res += gc;
			if(a && !b) sm[j] += gc;
			if(!a && b) sm[i] += gc;
			if(!a && !b) res += gc; 
		} 
	}
	for(int i = 1; i <= now; i++)
	if(find(i) == i){
		++res; res += sm[i] == 0 ? 0 : sm[i] - 1;
	} else sm[find(i)] += sm[i];
	res -= now; return ksm(2, res);
}
void dfs(int las, int rest, int coef){
	if(!rest){ Add(ans, mul(coef, calc())); return; }
	for(int i = min(las, rest); i; i--){
		++ct[i]; loop[++now] = i;
		dfs(i, rest - i, mul(coef, mul(inv[i], inv[ct[i]])));
		--ct[i]; --now;
	}
}
int main(){
	cin >> n; inv[0] = inv[1] = 1;
	for(int i = 2; i <= n; i++) inv[i] = mul(Mod-Mod/i, inv[Mod%i]);
	for(int i = 1; i <= n; i++) for(int j = 1; j <= n; j++) g[i][j] = gcd(i, j);
	dfs(n, n, 1);
	cout << ans; return 0;
}
发布了610 篇原创文章 · 获赞 94 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/sslz_fsy/article/details/103431173