【LG4708】画画

【LG4708】画画

题面

洛谷

题解

存在欧拉回路的充要条件是每个点度数为偶数,首先将无标号转化为有标号,那么两张本质相同的图必然有一个排列 (p),使得发生 (i o p_i) 的映射之后两图一模一样。进而考虑枚举每个排列 (p),注意到由排列 (p) 发生的映射可以看作一个置换的形式,那么可以将每个排列 (p) 下的不动点个数算出来,有 burnside 引理,所有排列 (p) 下不动点个数之和再乘上 (frac 1{n!}) 就是答案。
直接枚举 (p) 显然是很呆的,而且注意到我们所关注的也只有一个排列所包含的环长的可重集,那么我们可以在数据范围允许的条件下,用 (O(n的划分数)) 的复杂度枚举所有环长的可重集。
然后可以列出答案的式子:

[ans=frac 1{n!}sum_{1leq l_1leq l_2...leq l_kleq n,sum l_i=n} frac {n!prod_{i=1}^k(l_i-1)!}{prod_{i=1}^n c_i!prod_{i=1}^kl_i!}f(l_1,l_2...l_k) ]

其中 (c_i) 为环长为 (i) 环的个数,(f) 为当前环长集合的不动点个数,式子所代表的意义就是可重组合的方案数 (frac {n!}{prod _{i=1}^kl_i!}),除掉等价的相等环长的方案数 (prod_{i=1}^nc_i!),因为环上的标号不知道所以再乘上每个环标号的方案数 (prod_{i=1}^k(l_i-1)!),然后 (f) 就是不动点个数。

接下来的重点是考虑如何算 (f),也就是这 (n) 个点连边的方案数,因为题目限制的是奇偶性,所以我们算方案数也应该据此讨论。

  • 首先考虑环内部的方案数:
    (l_i) 为奇数,那么由于是该置换下的不动点,我们连环内两个点必然要“联动”地把环内所有距离相等的点连上边,而且每个点奇偶性不影响,有 (2^{lfloorfrac {l_i}2 floor}) 种方案。
    (l_i) 为偶数,距离小于 (lfloorfrac {l_i}2 floor) 时和奇数的情况一样有 (2^{lfloorfrac {l_i}2 floor}) 种方案,否则的话环内每个点的奇偶性将发生一次改变。
  • 其次是环与环之间的方案数:
    不妨设两环环长分别为 (l_i,l_j),那么对于两环之间的点对,由简单数论常识可以知道,一次“联动”发生的点对数为 ( ext{lcm}(l_i,l_j)),又因为共有 (l_il_j) 对点,且“联动”不交,那么“联动”的个数就为 (gcd(l_i,l_j)),那么对于 (i,j) 上的点,每次“联动”给这些点上连的边的条数相同,那么 (i) 中每次连的边数 (e_i=frac {l_j}{gcd(l_i,l_j)})(j) 中每次连的边数 (e_j=frac {l_i}{gcd(l_i,l_j)})
    (e_i,e_j) 均为偶,那么不会对奇偶性发生影响。
    (e_i,e_j) 奇偶都存在,那么会对某个 (i)(j) 的奇偶性发生影响。
    (e_i,e_j) 均为奇,那么会对两环之间的奇偶性同时发生影响。

综合环内与环间发生的变化,注意到每次发生变化都是环整体变化,所以我们可以转化下原问题:
有一个 (x) 个点的无向图,每个点有不同的 (a_i) 次机会异或 (1),两点间有 (b_i) 条不同的边,每条边有 (1) 次机会使两端的点异或 (1),问开始点权均为 (0),最后也为 (0) 的操作方案数。
首先点异或次数总和应为偶数,那么点任意异或的方案数为 (2^{max {-1+sum a_i,0}}),考虑图的一棵生成树,观察可知对于所有非树边和点任意异或的方案可以唯一对应树上一种合法的异或的方案数,那么边上的方案数为 (2^{sum b_i-x+1}),两者相乘就是我们转化问题的答案。

至此,那么求解 (f) 就变得简单了,需要注意的细节是对于不影响奇偶性的操作要记得直接乘上 (2) 的幂次,对于某个 (f) 它转化后的图有可能不连通(计算方法类似)。

最后直接 dfs 枚举划分再 (O(n^2)) 计算就可以了。

代码

#include <bits/stdc++.h>
using namespace std;
const int Mod = 998244353;
int upd(int x) { return x + (x >> 31 & Mod); }
int fpow(int x, int y) {
	int res = 1;
	while (y) {
		if (y & 1) res = 1ll * res * x % Mod;
		x = 1ll * x * x % Mod, y >>= 1;
	}
	return res;
}
int fac[55], ifc[55];
vector<int> vec;
int N, ans, cnt[55], pa[55], sz[55], g[55][55];
int getf(int x) { while (x != pa[x]) x = pa[x] = pa[pa[x]]; return x; }
int solve() {
	int res = 1, tot = 0;
	for (int i = 1; i <= N; i++) res = 1ll * res * ifc[cnt[i]] % Mod;
	for (int i : vec) res = 1ll * res * ifc[i] % Mod * fac[i - 1] % Mod;
	iota(&pa[0], &pa[vec.size()], 0);
	fill(&sz[0], &sz[vec.size()], 0);
	for (int i = 0; i < (int)vec.size(); i++)
		tot += (vec[i] - 1) >> 1, sz[i] += (vec[i] & 1) ^ 1;
	for (int i = 0; i < (int)vec.size(); i++)
		for (int j = i + 1; j < (int)vec.size(); j++) {
			int d = g[vec[i]][vec[j]], e1 = vec[j] / d & 1, e2 = vec[i] / d & 1;
			if (e1 && e2) pa[getf(i)] = getf(j), tot += d;
			if (!e1 && e2) sz[j] += d;
			if (e1 && !e2) sz[i] += d;
			if (!e1 && !e2) tot += d;
		}
	for (int i = 0; i < (int)vec.size(); i++)
		if (i != pa[i]) sz[getf(i)] += sz[i];
	for (int i = 0; i < (int)vec.size(); i++)
		if (i == pa[i]) tot += max(0, sz[i] - 1), ++tot;
	tot -= vec.size();
	res = 1ll * res * fpow(2, tot) % Mod;
	return res;
}
void dfs(int x, int mx) {
	if (!x) return (void)(ans = upd(ans + solve() - Mod));
	if (x < mx) return ;
	for (int i = mx; i <= x; i++)
		cnt[i]++, vec.push_back(i), dfs(x - i, i), cnt[i]--, vec.pop_back();
}
int main () {
#ifndef ONLINE_JUDGE
    freopen("cpp.in", "r", stdin);
#endif
	for (int i = fac[0] = 1; i <= 50; i++) fac[i] = 1ll * fac[i - 1] * i % Mod;
	ifc[50] = fpow(fac[50], Mod - 2);
	for (int i = 49; ~i; i--) ifc[i] = 1ll * ifc[i + 1] * (i + 1) % Mod;
	for (int i = 1; i <= 50; i++)
		for (int j = 1; j <= 50; j++) g[i][j] = __gcd(i, j);
	cin >> N;
	dfs(N, 1);
	printf("%d
", ans);
    return 0;
}
原文地址:https://www.cnblogs.com/heyujun/p/14452653.html