P4708 画画

tag:burnside引理,组合计数,欧拉回路


要知道 \(n=50\) 的复杂度可能是拆分数。。

本体同构的定义是存在一个标号的置换,使得图同构,所以根据老套路把枚举点的置换优化为枚举轮换拆分方案,这部分直接dfs,复杂度为 \(50\) 的拆分数。

根据欧拉回路的性质,存在欧拉回路等价于所有点度数都是偶数,所以只需要关心点度数的奇偶性就行。

先考虑一个轮换自身的贡献,在一个大小为 \(len\) 的等价类内,一个点会出现 \(2\) 次,一共有 \(\lfloor\frac{n-1}2\rfloor\) 个这样的等价类;而如果 \(len\) 是偶数的话,还会有一个大小为 \(\frac {len}2\) 的等价类使得每个点只出现 \(1\) 次。


然后考虑一对轮换的贡献,一个等价类的大小为 \(lca(a,b)\),一共有 \(\gcd(a,b)\) 个,\(a\) 中的点每个出现了 \(\frac b{gcd}\) 次,\(b\) 中的点每个出现了 \(\frac a{gcd}\) 次。

如果两边的点都出现了偶数次,那么这些边不会影响奇偶性;

如果只有一边的点出现了奇数次,可以看作是那一边的点有 \(gcd\) 次操作可以改变自己的奇偶性;

如果两边的点都出现了奇数次,可以看作是有 \(gcd\) 次操作可以同时改变两边的奇偶性。

先乘上不会影响奇偶性的部分,剩下的可以把一个轮换看成一个点,将上述第三种情况的点对连一条边,然后分连通块计算。

然后问题变为,对于一个连通块,每个点有 \(p_i\) 次操作可以改变自己的奇偶性,每条边有 \(e_i\) 次操作可以改变自己的奇偶性,求最后所有点都是偶数的方案数。

可以任意找一个生成树,然后只要点和非树边确定了,且所有点度数加起来是偶数,那么方案就存在且唯一。(从叶子节点开始确定每一条树边,如果当前点度数为奇数,就使用它和父亲的那条树边)

所以贡献就是 \(2^{\max(\sum p_i-1,0)+\sum e_i-(size-1)}\)


复杂度 \(O(50\text{拆分数}\cdot 50^2)\)

#include<bits/stdc++.h>
using namespace std;

template<typename T>
inline void Read(T &n){
	char ch; bool flag=false;
	while(!isdigit(ch=getchar())) if(ch=='-')flag=true;
	for(n=ch^48; isdigit(ch=getchar()); n=(n<<1)+(n<<3)+(ch^48));
	if(flag) n=-n;
}
#define no !

enum{
	MOD = 998244353
};

inline int ksm(int base, int k=MOD-2){
	int res=1;
	while(k){
		if(k&1)
			res = 1ll*res*base%MOD;
		base = 1ll*base*base%MOD;
		k >>= 1;
	}
	return res;
}

inline int inc(int a, int b){
	a += b;
	if(a>=MOD) a -= MOD;
	return a;
}

inline void iinc(int &a, int b){a = inc(a,b);}

int a[51], top;

int gcd[51][51];
int fa[51], cnt[51][51];
int Find(int x){return fa[x]==x?x:fa[x]=Find(fa[x]);}

int id[51];
inline bool cmp(const int &u, const int &v){return Find(u)<Find(v);}

int invjc[51], inv[51];

int ans;
inline void solve(){
	int tmp=1;
	for(int i=1, tot=1; i<=top; i++){
		if(i==top or a[i]!=a[i+1]) tmp = 1ll*tmp*invjc[tot]%MOD, tot = 1;
		else tot++;
		tmp = 1ll*tmp*inv[a[i]]%MOD;
	}
	memset(cnt,0,sizeof cnt);
	for(int i=1; i<=top; i++) fa[i] = id[i] = i;
	int pw=0;
	for(int i=1; i<=top; i++) pw += (a[i]-1)/2, cnt[i][i] = (a[i]-1)&1;
	for(int i=1; i<=top; i++) for(int j=i+1; j<=top; j++){
		int vi = a[j]/gcd[a[i]][a[j]], vj = a[i]/gcd[a[i]][a[j]], dlt = gcd[a[i]][a[j]];
		if(vi&1)
			if(vj&1) cnt[i][j] = dlt, fa[Find(i)] = Find(j);
			else cnt[i][i] += dlt;
		else
			if(vj&1) cnt[j][j] += dlt;
			else cnt[i][j] = 0, pw += dlt;
	}
	sort(id+1,id+top+1,cmp);
	tmp = 1ll*tmp*ksm(2,pw)%MOD;
	for(int l=1, r; l<=top; l=r+1){
		r=l; while(r<top and Find(id[r+1])==Find(id[r])) r++;
		int cntp=0, cnte=0;
		for(int i=l; i<=r; i++) cntp += cnt[id[i]][id[i]];
		for(int i=l; i<=r; i++) for(int j=l; j<=r; j++) if(i!=j) cnte += cnt[id[i]][id[j]];
		tmp = 1ll*tmp*ksm(2,max(cntp-1,0)+cnte-(r-l))%MOD;
	}
	iinc(ans,tmp);
}

void dfs(int rem, int prv){
	if(!rem) return solve();
	if(rem<prv) return;
	for(int i=prv; i<=rem; i++)
		a[++top] = i, dfs(rem-i,i), top--;
}

int Gcd(int a, int b){return b?Gcd(b,a%b):a;}

int main(){
	int n; Read(n);
	for(int i=1; i<=n; i++) for(int j=1; j<=n; j++) gcd[i][j] = Gcd(i,j);
	invjc[0] = 1; for(int i=1; i<=n; i++) inv[i] = ksm(i), invjc[i] = 1ll*invjc[i-1]*inv[i]%MOD;
	dfs(n,1);
	cout<<ans<<'\n';
	return 0;
}
posted @ 2021-06-28 23:25  oisdoaiu  阅读(30)  评论(0编辑  收藏  举报