P4708 画画 题解

代码稍微比 @Log_x 大佬的要短一点点。

可以枚举 \(n!\) 种置换,求出每种置换下的不动点个数。

而每条边又分在相同置换和不同置换内两种

相同置换内求不动点个数

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

不同置换内求不动点个数

考虑两个置换的大小为 \(A,B\),那么一条边会引申出 \(\operatorname{lcm}(A,B)\) 条边,于是不动点个数其实是 \(\gcd(A,B)\)

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

发现只与置换的大小有关,暴力枚举拆分,算每个拆分的方案数,先有一个排列的方案数 \(n!\),一个循环的贡献是 \(\frac{1}{len}\),大小相同的循环之间无序,有一个\(\frac{1}{cnt_{i}!}\)

考虑欧拉回路这个更强的限制,即每个点的度数为偶数,同样考虑一个置换内的边。

  • 若长度为奇数 \(2\times l+1\),所有置换出不出现对点都是偶数的影响,可以不管。
  • 若长度为偶数 \(2\times l\),其中 \(l-1\) 个不动点对奇偶性是没有影响的,而最后一个取所有对角线的不动点会使所有点度数加 \(1\)

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

我们发现现在可以先将贡献乘上没有影响的 \(2^{cnt}\)

然后可以有 \(d_id\) 种方法使第 \(i\) 个置换的度数全部 \(+1\)\(e_{u,v}\) 中方法使 \(u,v\) 置换的度数都 \(+1\)

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

但还有更好的方法。

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

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

点数为 \(n\),点的选择数为 \(s\),边的选择数为 \(m\),的一个连通块。
方案数就是 \(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){//dfs枚举
	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;
}

AC 记录
posted @ 2024-02-26 18:58  BadBadBad__AK  阅读(96)  评论(0)    收藏  举报