题目大意: 问有多少个不同构的 n n n 个点的图。
题解
A图的顶点经过一定的重新标号以后,A图的顶点集和边集要完全与B图一一对应。
这是题目里告诉我们的同构的条件,这应该算半个提示了,我们可以认为,重新标号是一种置换,这种置换形如:
(
1
2
.
.
.
n
−
1
n
a
1
a
2
.
.
.
a
n
−
1
a
n
)
\left( \begin{matrix} 1 & 2 & ... & n-1 & n\\ a_1 & a_2 & ... & a_{n-1} & a_n \end{matrix} \right)
(1a12a2......n−1an−1nan)
那么总的置换数量就是 ∣ G ∣ = n ! |G|=n! ∣G∣=n!,然后用burnside定理来求,也就是要求出每种置换有多少个不动点。
先将一个置换表示成若干个循环的乘积,然后考虑连边,假如循环 A A A 内第 x x x 个元素和第 y y y 个元素之间连了边,因为要在置换后图保持不变,所以第 x + 1 x+1 x+1 和第 y + 1 y+1 y+1 个元素之间也要有连边,同理,距离为 ∣ x − y ∣ |x-y| ∣x−y∣ 的元素之间都需要连边。
可以发现,对于一个长度 d d d,要么全部连边,要么全都不连,而 d d d 的取值范围是 [ 1 , ⌊ L i 2 ⌋ ] [1,\lfloor \frac {L_i} 2 \rfloor] [1,⌊2Li⌋],其中 L A L_A LA 是这个循环的长度,则一共有 2 ⌊ L i 2 ⌋ 2^{\lfloor \frac {L_i} 2 \rfloor} 2⌊2Li⌋ 种连边方案。
再考虑两个不同的循环 A A A 和 B B B,类似的,假如 A A A 中的第 i i i 个元素和 B B B 中的第 j j j 个元素之间连了边,那么 i + 1 i+1 i+1 和 j + 1 j+1 j+1 之间也要连,以此类推下去要连 l c m ( L A , L B ) lcm(L_A,L_B) lcm(LA,LB) 条边,不妨称这样的 ( i , j ) , ( i + 1 , j + 1 ) (i,j),(i+1,j+1) (i,j),(i+1,j+1) 为同一组边。
因为一共有 L A × L B L_A\times L_B LA×LB 条边,所以一共有 L A × L B l c m ( , L A ) , L B = gcd ( L A , L B ) \frac {L_A\times L_B} {lcm(,L_A),L_B}=\gcd(L_A,L_B) lcm(,LA),LBLA×LB=gcd(LA,LB) 组边,每组边也是要么连要么不连,方案数为 2 gcd ( L A , L B ) 2^{\gcd(L_A,L_B)} 2gcd(LA,LB)。
所以答案就是:
1
∣
G
∣
∑
L
1
,
L
2
,
.
.
.
,
L
k
(
∏
i
=
1
k
2
⌊
L
i
2
⌋
×
∏
1
≤
A
<
B
≤
k
2
gcd
(
L
A
,
L
B
)
)
\frac 1 {|G|}\sum_{L_1,L_2,...,L_k}\left(\prod_{i=1}^k2^{\lfloor \frac {L_i} 2 \rfloor}\times \prod_{1\leq A<B\leq k} 2^{\gcd(L_A,L_B)}\right)
∣G∣1L1,L2,...,Lk∑(i=1∏k2⌊2Li⌋×1≤A<B≤k∏2gcd(LA,LB))
但是这样的话时间复杂度为 O ( n ! × n 2 ) O(n!\times n^2) O(n!×n2),可以愉快地得到 T L E TLE TLE 的好成绩。
考虑优化,观察后可以发现,我们并不关心每个循环内的点是谁,只关心这个置换能拆成几个循环以及每个循环的大小 L i L_i Li。
于是考虑直接枚举 L i L_i Li,相当于将 n n n 进行自然数拆分,经暴力发现,当 n = 60 n=60 n=60 时,拆分方案大概只有 1 0 6 10^6 106 种,可以接受。
当我们枚举出所有 L i L_i Li 之后,就可以用上面的柿子求解,但是还需要求多一个东西:有多少个置换拆成循环之后,循环的长度分别是 L 1 , L 2 , . . . , L k L_1,L_2,...,L_k L1,L2,...,Lk。
这个很简单,排列组合一下就能得到:
先给这 k k k 个循环安排元素进去,方案数为 n ! ∏ i = 1 k L i ! \dfrac {n!} {\prod_{i=1}^k L_i!} ∏i=1kLi!n!。
再考虑每个循环内的元素顺序,不考虑重复的话就有 L i ! L_i! Li! 排列,但是对于一个循环而言, ( a 1 a 2 a 3 . . . a n ) (a_1a_2a_3...a_n) (a1a2a3...an) 和 ( a 2 a 3 a 4 . . . a n a 1 ) (a_2a_3a_4...a_na_1) (a2a3a4...ana1) 其实是同样的排列,所以要除以 L i L_i Li,即方案数为 L i ! L i = ( L i − 1 ) ! \dfrac {L_i!} {L_i}=(L_i-1)! LiLi!=(Li−1)!。
然后对于长度相同的两个循环,比如说 ( 1 2 ) ( 3 4 ) (1~2)(3~4) (1 2)(3 4),它其实等价于 ( 3 4 ) ( 1 2 ) (3~4)(1~2) (3 4)(1 2),设长度为 i i i 的循环有 S i S_i Si 个,那么还需要除以 ∏ i = 1 k S i ! \prod_{i=1}^k S_i! ∏i=1kSi!。
所以一共有 n ! ∏ i = 1 k L i × ∏ i = 1 k S i ! \frac {n!} {\prod_{i=1}^k L_i\times\prod_{i=1}^k S_i!} ∏i=1kLi×∏i=1kSi!n! 个置换满足要求。
枚举出 L L L 之后,把这个方案数和上面那个柿子乘起来就是答案了。以及,这里的 n ! n! n! 和上面的 1 ∣ G ∣ \frac 1 {|G|} ∣G∣1 可以抵消掉。
设对 n n n 进行自然数拆分的时间复杂度为 K K K,那么最后的时间复杂度为 O ( K × n 2 ) O(K\times n^2) O(K×n2)。
讲道理,当 n = 60 n=60 n=60 的时候时间复杂度大概为 3.6 × 1 0 9 3.6\times 10^9 3.6×109,虽然说完全跑不满,但是实测也需要 3 s 3s 3s 左右,为什么能过呢……
太烧脑了果然还是不管好了qwq,代码如下:
#include <cstdio>
#define mod 997
#define maxn 110
int n;
int ksm(int x,int y)
{
int re=1;
while(y)
{
if(y&1)re=1ll*re*x%mod;
x=1ll*x*x%mod;y>>=1;
}
return re;
}
int fac[maxn],inv_fac[maxn],inv[maxn],gcd[maxn][maxn];
int gcd_(int x,int y){return y==0?x:gcd_(y,x%y);}
void work()
{
fac[0]=inv_fac[0]=inv[1]=1;
for(int i=1;i<=n;i++)fac[i]=1ll*fac[i-1]*i%mod;
inv_fac[n]=ksm(fac[n],mod-2);
for(int i=n-1;i>=1;i--)inv_fac[i]=1ll*inv_fac[i+1]*(i+1)%mod;
for(int i=2;i<=n;i++)inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)gcd[i][j]=gcd_(i,j);
}
int L[maxn],S[maxn],ans=0;
void work(int k)
{
int prod=1;
for(int i=1;i<=k;i++)prod=1ll*prod*((1ll<<(L[i]/2))%mod)%mod;
for(int i=1;i<=k;i++)for(int j=i+1;j<=k;j++)prod=1ll*prod*(1ll<<gcd[L[i]][L[j]])%mod;
for(int i=1;i<=k;i++)prod=1ll*prod*inv[L[i]]%mod;
for(int i=1;i<=n;i++)prod=1ll*prod*inv_fac[S[i]]%mod;
ans=(ans+prod)%mod;
}
int min(int x,int y){return x<y?x:y;}
void dfs(int x,int k)
{
if(!x)return (void)work(k-1);
for(int i=min(L[k-1],x);i>=1;i--)
L[k]=i,S[i]++,dfs(x-i,k+1),S[i]--;
}
int main()
{
scanf("%d",&n);work();
L[0]=n;dfs(n,1);
printf("%d",ans);
}