Luogu P4128 [SHOI2006]有色图

题意与数据范围

求 \(n\) 个点不同构的简单无向图的数目,答案对 \(997\) 取模

\(A\) 图与 \(B\) 图被认为是同构的是指:\(A\) 图的顶点经过一定的重新标号以后,\(A\) 图的顶点集和边集要完全与 \(B\) 图一一对应

\(0\le n\le 60\)

Solution

我们把无向图点的每一种重新排布的方式看作一种置换,则该置换群 \(G\) 的大小显然为 \(n!\)

对于置换群 \(G\) 中的每一个置换 \(g\) ,在 \(g\) 的作用下的不动点即为这样的一种连边方案:
\[
\forall (a,b)\in (V,E),\exist (P_a,P_b)\in (V,E)
\]
其中 \((a,b)\) 表示一条从 \(a\) 到 \(b\) 的无向边,\((V,E)\) 表示一张 \(V\) 个节点,\(E\) 条边的无向图,\(P_x\) 表示在置换 \(g\) 中编号为 \(x\) 的点所对应的置换

定义 \(X\) 为所有连边方案的集合,我们把在置换 \(g\) 下拥有上述性质的方案集合称为 \(X^g\) ,那么根据 \(\text{Burnside}\) 引理,答案即为 \(\frac{1}{|G|}\sum\limits_{g\in G}|X^g|\)

考虑如何计算 \(|X^g|\)

首先我们来考虑一下对于一个置换 \(g\) ,我们将它分解成若干个循环的乘积后,每一个循环内部的不动点如何计算

假设现在我们有一个循环 \(A\),其中第 \(i\) 个元素为 \(A_i\),假如 \(A_i\) 与 \(A_j\) 之间有一条边,那么所有下标“相距” \(|i-j|\) 的元素之间都必须有一条边,所以共有 \(\lfloor \frac{x}{2} \rfloor\) 种不同类型的边,对于同一种类型的边,我们要不都连,要么都连,所以对于一个大小为 \(x\) 的循环,内部有 \(2^{\lfloor \frac{x}{2} \rfloor}\) 种连边方案

现在我们再考虑两个大小分别为 \(x\) 、\(y\) 的循环之间的影响

显然,若我们在 \(x\) 中的第 \(i\) 个元素与在 \(y\) 中的第 \(j\) 个元素之间连了一条边,那么对于 \((i+1,j+1),(i+2,j+2)...\) 直至 \(i\) 与 \(j\) 再次连边。那么一共要连 \(lcm(x,y)\) 条边,而我们一共有 \(xy\) 种对应方案,所以一共有 \(\frac{xy}{lcm(x,y)}\) 种边,即 \(\gcd(x,y)\) 种边,所以这些循环之间产生的贡献就是 \(2^{\gcd(x,y)}\)

那么我们现在可以考虑枚举置换 \(g\) ,设其分解成的第 \(i\) 个循环的元素集合为 \(g_i\) ,那么答案就是
\[
\frac{1}{|G|}\sum\limits_{g\in G}(\prod\limits_{i=1}^{|g|}2^{\lfloor \frac{|g_i|}{2} \rfloor}\prod\limits_{i<j\le |g|}2^{\gcd(|g_i|,|g_j|)})
\]
不幸的是,由于要枚举整个置换群,这样的复杂度是 \(O(n!)\) 的

我们换个角度考虑

可以发现,我们只关心置换在分解成若干个循环的乘积后每个循环的大小,而并不在意这些循环究竟包含了哪些元素,所以我们可以考虑枚举每个置换可能是由哪些循环乘起来的

这个可以通过搜索求出,复杂度是自然数划分的方案数,可以接受

对于一种大小为 \(k\) 的划分方案,设 \(L_i\) 表示其中第 \(i\) 个循环的长度

首先考虑为这 \(k\) 个循环安排它们的位置,这步的方案数是带重复元素的排列数,即 \(\dbinom{n}{L_1\ L_2\ L_3 \ ...\ L_k}\)

然后我们再来考虑这 \(k\) 个循环内部的安排方式

直接 \(L_i!\) 肯定是不行的,因为这样无法保证它不能再被分解成更小的循环

但其实这也很简单,我们只要每次选出一个元素并在除了它以外且没有选过的元素中选择一个就可以了,所以这一步的方案数实际上是 \((L_i-1)!\)

两式相乘,得 \(\frac{n!}{\prod\limits_{i=1}^{k}L_i}\)

但这还不止,如果我们枚举到两个大小相同的循环,那么我们会把它们交换后的方案也算上,举例而言,就是:

如果有两个循环 \((2\ 1)(4\ 3)\) ,我们把它们交换一下,有 \((4\ 3)(2\ 1)\) ,然而这两个玩意儿是本质相同的,所以如果用 \(C_i\) 来表示一个置换中长度为 \(i\) 的循环的个数,那么方案数最后还得除上 \(\prod\limits_{i=1}^{n}C_i!\)

所以我们最后得到将 \(k\) 个长度分别为 \(L_1,L_2,...,L_k\) 的循环安放进去的方案数为
\[
\frac{n!}{\prod\limits_{i=1}^{k}L_i\prod\limits_{i=1}^{n}C_i!}
\]
而安放完这些循环后,我们还得再乘上之前分析过的循环内部及循环之间产生的贡献数,得到最后的答案为
\[
\sum\limits_{\ \ \sum\limits_{i=1}^{k}L_i=n,\\L_1\ge L_2\ge...\ge L_k}\frac{\prod\limits_{i=1}^{k}2^{\lfloor \frac{L_i}{2} \rfloor}\prod\limits_{i<j\le k}2^{\gcd(L_i,L_j)}}{\prod\limits_{i=1}^{k}L_i\prod\limits_{i=1}^{n}C_i!}
\]
其中 \(|G|\) 与 \(n!\) 抵消了,然后直接计算即可

复杂度为 \(O(B_n\times n^2)\) ,\(B_n\) 为 \(n\) 的自然数划分的方案数

代码如下:

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
const int N=1e2+10;
const int mod=997;
int n,L[N],bin[N],fac[N],gcd[N][N],ans,C[N];
inline void Add(int &x,int y){x+=y;x-=x>=mod? mod:0;}
inline int MOD(int x){x-=x>=mod? mod:0;return x;}
inline int Minus(int x){x+=x<0? mod:0;return x;}
inline int exgcd(int x,int y){int r;if(y)swap(x,y);while(x&&y)r=x%y,x=y,y=r;return x;}
inline int fas(int x,int p){int res=1;while(p){if(p&1)res=1ll*res*x%mod;p>>=1;x=1ll*x*x%mod;}return res;}
inline void Calc(int m){
    int sum=1;
    for(register int i=1;i<=m;i++)sum=1ll*sum*bin[L[i]>>1]%mod;
    for(register int i=1;i<=m;i++)
        for(register int j=i+1;j<=m;j++)
            if(i!=j)sum=1ll*sum*bin[gcd[L[i]][L[j]]]%mod;
    int fm=1;
    for(register int i=1;i<=m;i++)fm=1ll*fm*L[i]%mod;
    memset(C,0,sizeof(C));
    for(register int i=1;i<=m;i++)C[L[i]]++;
    for(register int i=1;i<=n;i++)fm=1ll*fm*fac[C[i]]%mod;
    Add(ans,1ll*sum*fas(fm,mod-2)%mod);
}
inline void DFS(int rest,int las,int num){
    if(!rest){Calc(num-1);return;}
    for(register int i=1;i<=min(rest,las);i++)
        L[num]=i,DFS(rest-i,i,num+1);
}
inline void Preprocess(){
    for(register int i=1;i<=n;i++)
        for(register int j=1;j<=n;j++)
            gcd[i][j]=exgcd(i,j);
    fac[0]=1;for(register int i=1;i<=n;i++)fac[i]=1ll*fac[i-1]*i%mod;
    bin[0]=1;for(register int i=1;i<=n;i++)bin[i]=2ll*bin[i-1]%mod;
}
int main(){
    scanf("%d",&n);if(!n){puts("1");return 0;}
    Preprocess();DFS(n,n,1);printf("%d\n",ans);
    return 0;
}

原文地址:https://www.cnblogs.com/ForwardFuture/p/11478566.html

时间: 2024-11-08 20:26:44

Luogu P4128 [SHOI2006]有色图的相关文章

[SHOI2006] 有色图

题面在这里! 和上个题就是一样的啦,只不过颜色数从2变成了k,模数不再固定(不可以打表啦2333) #include<bits/stdc++.h> #define ll long long using namespace std; inline int add(int x,int y,const int ha){ x+=y; return x>=ha?x-ha:x;} inline void ADD(int &x,int y,const int ha){ x+=y; if(x&g

bzoj 1815: [Shoi2006]color 有色图 置换群

1815: [Shoi2006]color 有色图 Time Limit: 4 Sec  Memory Limit: 64 MBSubmit: 136  Solved: 50[Submit][Status] Description Input 输入三个整数N,M,P 1< = N <= 53 1< = M < = 1000 N< P < = 10^ 9 Output 即总数模P后的余数 Sample Input input 1 3 2 97 Sample Output

BZOJ 1815: [Shoi2006]color 有色图 [Polya DFS 重复合并]

传送门 题意: 染色图是无向完全图,且每条边可被染成k种颜色中的一种.两个染色图是同构的,当且仅当可以改变一个图的顶点的编号,使得两个染色图完全相同.问N个顶点,k种颜色,本质不同的染色图个数(模质数N≤53,P<109). 想了一节课和一中午又看了课件 相同类型的循环合并的想法很巧妙 首先,点的置换对应唯一边的置换,我们可以枚举所有点的置换,找出每个置换下边置换的循环有多少个,然后套$Polya$公式 但是复杂度带叹号 我们发现,很多点置换类型是一样的,我们可以对$n$搜索划分来枚举点置换的类

luogu P3799 妖梦拼木棒

二次联通门 : luogu P3799 妖梦拼木棒 /* luogu P3799 妖梦拼木棒 用一个桶存下所有的木棒 美剧两根短的木棒长度 后随便乘一乘就 好了.. */ #include <algorithm> #include <cstdio> #define Mod 1000000007 #define Max 5000 void read (int &now) { now = 0; register char word = getchar (); while (wo

[luogu P1967][NOIp2013]P1967 货车运输

题目描述 A 国有 n 座城市,编号从 1 到 n,城市之间有 m 条双向道路.每一条道路对车辆都有重量限制,简称限重.现在有 q 辆货车在运输货物, 司机们想知道每辆车在不超过车辆限重的情况下,最多能运多重的货物. 输入输出格式 输入格式: 输入文件名为 truck.in. 输入文件第一行有两个用一个空格隔开的整数 n,m,表示 A 国有 n 座城市和 m 条道 路. 接下来 m 行每行 3 个整数 x. y. z,每两个整数之间用一个空格隔开,表示从 x 号城市到 y 号城市有一条限重为 z

luogu 3126 回文的路径

https://www.luogu.org/problem/show?pid=3126 考虑dp,从两头走到中间. f[i][j][k][l]表示从左上角走到(i,j),从右下角走到(k,l),路径长度相等,所经过路径相同的方案数. 方程不再赘述. 考虑步数要相同,所以只要枚举步数和行就好. f[i][j][k]表示第一个点在第j行,第2个点在第k行,走i步的方案数. 所以得出方程f[i][j][k]=(f[i-1][j-1][k]+f[i-1][j][k+1]+f[i-1][j-1][k+1]

luogu P2018 消息传递

二次联通门 : luogu P2018 消息传递 /* luogu P2018 消息传递 树形dp 原来用优先队列做了一下, T了俩点 MMP 去看正解.. 复杂度一样好不好.. 每次到达一个点,记录其子树中所有的dp值 优先向大的一边转移 */ #include <cstdio> #include <iostream> #include <algorithm> #include <cstring> #define INF 1e8 const int BUF

luogu P1966 火柴排队

二次联通门 : luogu P1966 火柴排队 /* luogu P1966 火柴排队 神TM逆序对... noip怎么这么坑啊.. 暴力都没得打 此题模拟考试时爆了0 做法 将A数组排序,由于B数组与A数组是一一对应的 那么B数组的位置也会发生相应的变化 此时B数组逆序数对数即为答案 */ #include <cstdio> #include <iostream> #include <algorithm> const int BUF = 123123123; cha

luogu P1941 飞扬的小鸟

二次联通门 : luogu P1941 飞扬的小鸟 /* luogu P1941 飞扬的小鸟 dp 向上飞是完全背包,向下掉就是01背包 分情况讨论一下 最后合并一下 */ #include <cstdio> #include <iostream> #include <cstring> const int BUF = 123123123; char Buf[BUF], *buf = Buf; inline void read (int &now) { for (