Schreier-Sims 算法的阐述
在讲解该算法前,假定读者已经理解了 Burnside 引理 的前置定理
——拉格朗日定理 和 轨道-稳定子定理。
给定若干
根据拉格朗日定理,求出强生成元后,置换群的阶数就是所有截面大小的乘积。
使用 Schreier-Sims 算法求出强生成元即可解决本题。
注意计算截面大小的乘积时需要高精度。
/*
this code is made by warzone
2024-09-08 23:47
*/
#include<stdio.h>
#include<string.h>
#include<math.h>
#include<vector>
typedef unsigned int word;
struct READ{//快读快写
char c,w;
inline READ(){c=getchar();}
template<typename type>
inline READ& operator >>(type& num){
while('0'>c||c>'9') c=getchar();
for(num=0;'0'<=c&&c<='9';c=getchar())
num=num*10+(c-'0');
return *this;
}
}cin;
const word W=1e7;
struct BigInt{//高精度类
word num[16];
inline void operator*=(const word x){
for(word i=0,get=0;i<16;++i){
get+=num[i]*x;
num[i]=get%W,get/=W;
}
}
inline void print()const{
word i=15;
while(i&&!num[i]) --i;
printf("%u",num[i]);
while(i--){
putchar(num[i]/1000'000+'0');
putchar((num[i]/100'000)%10+'0');
putchar((num[i]/10'000)%10+'0');
putchar((num[i]/1000)%10+'0');
putchar((num[i]/100)%10+'0');
putchar((num[i]/10)%10+'0');
putchar(num[i]%10+'0');
}
}
}a;
word n,m;
struct perm{//置换类
word num[64];
inline perm(){}
inline perm(const perm &p)=default;
inline perm(const perm &p,const perm &q){//置换的复合
for(word i=0;i<=n;++i)
num[i]=p.num[q.num[i]];
}
inline perm _1()const{//逆置换
perm p;
for(word i=0;i<=n;++i)
p.num[num[i]]=i;
return p;
}
}p;
std::vector<perm> section[64];//section[n] G_n 的截面
word norm[64][64];
//norm[n][m] 满足 g(n)=m 的 g 对应的标准元素(在 section[n])中的编号
inline void insert(perm &p,const word n){
// Schreier-Sims 算法主体,尝试插入元素 p(n 元置换)
word m=n;
for(;m>1&&norm[m][p.num[m]]!=0xffff'ffff;--m) if(p.num[m]!=m){
p=perm(section[m][norm[m][p.num[m]]]._1(),p);
if(p.num[m]!=m) break;
}//检查 p 是否在群里
if(m==1) return;//已经在群内
const word siz=section[n].size();
for(word i=0;i<siz;++i){
word &get=norm[n][p.num[section[n][i].num[n]]];
if(get==0xffff'ffff){
get=section[n].size();
section[n].push_back(perm(p,section[n][i]));
}
}//检查 G_n 的截面是否有扩充
if(siz==section[n].size()){// G_n 的截面没有扩充的情况
insert(p,n-1);
for(word i=1;i<siz;++i){
perm q(p,section[n][i]);
q=perm(section[n][norm[n][q.num[n]]]._1(),q);
//通过 Schreier 引理新增生成元
insert(q,n-1);//尝试插入新生成元
}
return;
}
for(word i=siz;i<section[n].size();++i)
for(word j=1;j<=n;++j)
for(word k=1;k<section[j].size();++k){
word &get=norm[n][section[j][k].num[section[n][i].num[n]]];
if(get==0xffff'ffff){
get=section[n].size();
section[n].push_back(perm(section[j][k],section[n][i]));
}
}//使用搜索扩充 G_n 的截面
std::vector<perm> add;
for(word i=siz;i<section[n].size();++i){
for(word j=1;j<=n;++j)
for(word k=1;k<section[j].size();++k){
const perm p(section[j][k],section[n][i]);
add.push_back(perm(section[n][norm[n][p.num[n]]]._1(),p));
}
for(word j=0;j<siz;++j){
const perm p(section[n][i],section[n][j]);
add.push_back(perm(section[n][norm[n][p.num[n]]]._1(),p));
}
}//通过 Schreier 引理新增生成元
for(auto &p:add) insert(p,n-1);//尝试插入新生成元
}
int main(){
memset(norm,0xff,sizeof(norm));
cin>>n>>m;
for(word i=1;i<=n;++i) p.num[i]=i,norm[i][i]=0;
for(word i=1;i<=n;++i)//初始时,每个截面只有恒等置换
section[i].push_back(p);
for(word i=1;i<=m;++i){//逐个尝试插入新生成元
for(word j=1;j<=n;++j) cin>>p.num[j];
insert(p,n);
}
a.num[0]=1;
for(word i=1;i<=n;++i)//计算截面大小乘积
a*=section[i].size();
a.print();
return 0;
}