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;
}