我们可以把每条边存在或者不存在看成是黑和白两种颜色
然后这个题就充斥着一股ploya定理的气息
但是有关边的置换太蛋疼,我们考虑把点的置换对应到边上
找找规律,我们发现对于一个点的置换,如果其存在一个循环节大小为x,那么这x个点的边之间会形成x/2个循环节,如果其存在两个循环节大小分别为x和y,那么两个循环节里的点之间的边会形成gcd(x,y)个循环节
我们考虑爆搜点置换,每次枚举当前最大的循环节多大,有多少个(不要问我为什么这么搜复杂度是对的,我并不懂)
那么假设当前有cnt种循环节,每种的大小是v[i],每种有t[i]个,那么这样的置换一共就有n!/v[1]v[2]v[3]...v[cnt]t[1]!t[2]!t[3]!...t[cnt]!种
然后就可以算了
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<ctime>
#include<cmath>
#include<algorithm>
#include<iomanip>
#include<vector>
#include<map>
#include<set>
#include<bitset>
#include<queue>
#include<stack>
using namespace std;
#define MAXN 70
#define MAXM 1010
#define INF 1000000000
#define MOD 997
#define eps 1e-8
#define ll long long
int fac[MAXN],ine[MAXN],inv[MAXN];
int n;
int v[MAXN],t[MAXN];
int tot;
int ans;
int gcd(int x,int y){
return !y?x:gcd(y,x%y);
}
int mi(int x,int y){
int re=1;
while(y){
if(y&1){
(re*=x)%=MOD;
}
(x*=x)%=MOD;
y>>=1;
}
return re;
}
void dfs(int x,int rem){
int i,j;
if(!rem){
int tmp=fac[n];
int cnt=0;
for(i=1;i<=tot;i++){
(tmp*=ine[t[i]]%MOD)%=MOD;
for(j=1;j<=t[i];j++){
(tmp*=inv[v[i]])%=MOD;
}
}
for(i=1;i<=tot;i++){
cnt+=t[i]*(v[i]/2);
cnt+=t[i]*(t[i]-1)/2*v[i];
for(j=1;j<i;j++){
cnt+=t[i]*t[j]*gcd(v[i],v[j]);
}
}
(ans+=tmp*mi(2,cnt))%=MOD;
return ;
}
if(x>rem){
return ;
}
dfs(x+1,rem);
for(i=1;i*x<=rem;i++){
v[++tot]=x;
t[tot]=i;
dfs(x+1,rem-i*x);
tot--;
}
}
int main(){
int i;
fac[0]=ine[0]=inv[0]=inv[1]=1;
for(i=1;i<MAXN;i++){
fac[i]=fac[i-1]*i%MOD;
}
for(i=2;i<MAXN;i++){
inv[i]=(MOD-MOD/i)*inv[MOD%i]%MOD;
}
for(i=1;i<MAXN;i++){
ine[i]=inv[i]*ine[i-1]%MOD;
}
scanf("%d",&n);
dfs(1,n);
(ans*=ine[n])%=MOD;
printf("%d\n",ans);
return 0;
}
/*
*/