题目链接
https://www.luogu.org/problem/P4708
题解
看上去Luogu P4706-4709是Sdchr神仙出的一场比赛,一道水题和三道很有趣的题终于全过了纪念QAQ(然而后三道都看了题解)
以及为啥这题AC代码几乎全是打表。。
前置题目: BZOJ1488 求(n)个点无标号无向图个数。(欢迎阅读 https://www.cnblogs.com/suncongbo/p/11295453.html )
没做过的建议先去做一下那题。
这道题依然是枚举拆分数,然后现在的问题就是给定一些长度的轮换求有多少个点度均为偶数的图满足经过这些轮换作用后依然不变。
首先一个性质是,同一轮换内的所有点度数相同。(显然)
考虑轮换内部的边,假设这个轮换长度为(L=2s+1), 那么则有(s)种不同的边组,每一种边组会使得轮换内所有点度数(+2); 若轮换长度为(2s), 则有(s)种不同的边组,其中((s-1))种(除了对角的之外)使得所有点度数(+2), (1)种使得所有点度数(+1). 度数(+2)不改变奇偶性的显然可以不考虑,直接答案乘以(2).
再考虑轮换之间的边,假设两轮换长度分别为(a,b)则有(gcd(a,b))种边组,每种边组内包含( ext{lcm}(a,b))条边,会给(a)中的点度数(+frac{ ext{lcm}(a,b)}{a}),给(b)中的点度数(+frac{ ext{lcm(a,b)}}{b}). 若二者都为偶数,答案乘以(2^{gcd(a,b)}). 若二者中恰有一个为奇数,则相当于有(gcd(a,b))次机会给奇数的那个轮换改变奇偶性。若二者都是奇数,则相当于有(gcd(a,b))次机会给两个轮换同时改变奇偶性。
于是问题转化为: 有一张新图(新图里的点代表一个轮换),初始点权都为(0), 对每个点有(c_i)次机会改变它的奇偶性,对每条边有(d_i)次机会同时改变两端点的奇偶性。求有多少种方案使得最终所有点的点权为(0).
这个结论是,对于每一种改变点权总次数为偶数次的方案,都存在(2^{sum d_i-cnt+1})种边操作方案((cnt)为点数)与之对应。
感性理解,改变点权如果是奇数次显然不行,偶数次的话可以构建一棵DFS树,非树边任意操作之后树边按照自下而上操作总能操作完。
注意这里官方题解写的是错的!官方题解直接写成(2^{sum d_i})坑了我一晚上……
那么答案就是(2^{sum c_i-1} imes 2^{sum d_i-cnt+1}).
问题解决。
时间复杂度同BZOJ 1488.
代码
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cassert>
#include<iostream>
#define llong long long
using namespace std;
inline int read()
{
int x=0; bool f=1; char c=getchar();
for(;!isdigit(c);c=getchar()) if(c=='-') f=0;
for(; isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+(c^'0');
if(f) return x;
return -x;
}
const int N = 50;
const int P = 998244353;
llong fact[N*N*N+3],finv[N*N*N+3],inv[N*N*N+3],pw2[N*N*N+3];
int gcd[N+3][N+3];
int GCD(int x,int y) {return y==0 ? x : GCD(y,x%y);}
llong quickpow(llong x,llong y)
{
llong cur = x,ret = 1ll;
for(int i=0; y; i++)
{
if(y&(1ll<<i)) {y-=(1ll<<i); ret = ret*cur%P;}
cur = cur*cur%P;
}
return ret;
}
void initmath(int n)
{
fact[0] = 1ll; for(int i=1; i<=n; i++) fact[i] = fact[i-1]*i%P;
finv[n] = quickpow(fact[n],P-2); for(int i=n-1; i>=0; i--) finv[i] = finv[i+1]*(i+1)%P;
for(int i=1; i<=n; i++) inv[i] = finv[i]*fact[i-1]%P;
pw2[0] = 1ll; for(int i=1; i<=n; i++) pw2[i] = pw2[i-1]*2ll%P;
}
int a[N+3],num[N+3];
int uf[N+3];
int c[N+3];
int n,cnt; llong ans;
int findfa(int u)
{
int i = u;
while(u!=uf[u]) {u = uf[u];}
while(u!=uf[i])
{
int j = uf[i]; uf[i] = u; i = j;
}
return u;
}
void unionfa(int u,int v)
{
int x = findfa(u),y = findfa(v);
if(x!=y) {uf[x] = y;}
}
llong calc()
{
for(int i=1; i<=cnt; i++) uf[i] = i,c[i] = 0;
llong ret = fact[n];
for(int i=1; i<=cnt; i++) {ret = ret*inv[a[i]]%P;}
for(int i=1; i<=n; i++) {ret = ret*finv[num[i]]%P;}
llong retp = 0ll;
for(int i=1; i<=cnt; i++)
{
for(int j=i+1; j<=cnt; j++)
{
int g = gcd[a[i]][a[j]],lcm = a[i]*a[j]/g;
int ci = lcm/a[i],cj = lcm/a[j];
if((ci&1)==(cj&1))
{
retp += g;
if(ci&1) {unionfa(i,j);}
}
}
}
for(int i=1; i<=cnt; i++)
{
for(int j=i+1; j<=cnt; j++)
{
int g = gcd[a[i]][a[j]],lcm = a[i]*a[j]/g;
int ci = lcm/a[i],cj = lcm/a[j];
int x = findfa(i),y = findfa(j);
if((ci&1)!=(cj&1))
{
if(ci&1) {c[x]+=g;}
else if(cj&1) {c[y]+=g;}
}
}
}
for(int i=1; i<=cnt; i++)
{
int x = findfa(i);
retp += ((a[i]-1)>>1);
if(!(a[i]&1)) {c[x]++;}
}
retp -= cnt;
for(int i=1; i<=cnt; i++) {if(uf[i]==i) retp++;}
for(int i=1; i<=cnt; i++)
{
if(uf[i]==i && c[i]>0)
{
retp += c[i]-1;
}
}
ret = ret*pw2[retp]%P;
return ret;
}
void dfs(int sum)
{
if(sum==0)
{
ans = (ans+calc())%P;
}
for(int i=a[cnt]; i<=sum; i++)
{
cnt++; a[cnt] = i; num[i]++;
dfs(sum-i);
num[i]--; a[cnt] = 0; cnt--;
}
}
int main()
{
initmath(N*N*N);
for(int i=1; i<=N; i++) for(int j=1; j<=N; j++) gcd[i][j] = GCD(i,j);
scanf("%d",&n);
a[0] = 1; dfs(n);
ans = ans*finv[n]%P;
printf("%lld
",ans);
return 0;
}