置换群总结

**~~

置换群总结

**~~
参考 1 2(强烈建议看一下论文)

基本概念

n次置换: 一个长度为 n n n的排列 a a a。对另一个长度为 n n n一个排列 b b b应用该置换, a [ i ] = a [ b [ i ] ] a[i]=a[b[i]] a[i]=a[b[i]]

置换的秩: 对排列 b : 1 , 2 , 3 , . . . , n b:1,2,3,...,n b:1,2,3,...,n应用 k k k次(最小的次数)置换 a a a,后,排列 b b b变回 1 , 2 , 3 , . . . , n 1,2,3,...,n 1,2,3,...,n

置换相乘: 置换 a a a乘置换 b b b,令结果为置换 c c c,则 c = b [ a [ i ] ] c=b[a[i]] c=b[a[i]]

置换的幂: 置换 a p a^p ap,置换a对 1 , 2 , 3 , . . . , n 1,2,3,...,n 1,2,3,...,n应用 p p p

轮换: 置换a的每一个连通分支称为一个轮换,特别的,大小为2的轮换称为对换。
例: ( 1 2 3 4 5 3 5 1 2 4 ) = [ 1 , 3 ] ⋅ [ 2 , 5 , 4 ] \begin{pmatrix} 1 &2 &3 &4 &5 \\ 3 &5 &1 &2 &4 \end{pmatrix}=[1,3]\cdot[2,5,4] (1325314254)=[1,3][2,5,4]
可利用轮换求置换的幂,如对长度 n n n的置换 a a a k k k次幂,取出所有轮换,假设一个为 b b b,则 a [ b [ i ] ] = a [ b [ i + k − 1 ] a[b[i]]=a[b[i+k-1]%n] a[b[i]]=a[b[i+k1]

置换与对应轮换的转换: 只考虑为单循环(多循环类似)的情况。对置换 a a a,先取 1 1 1,后为 a [ 1 ] , a [ a [ 1 ] ] . . . a[1],a[a[1]]... a[1],a[a[1]]...。操作完成即为对应轮换。而对轮换转换为置换,按照 a n s [ i ] = a [ i + 1 ] ans[i]=a[i+1] ans[i]=a[i+1]转换即可

置换群幂运算的性质

一般性结论

1.设 T k = e T^k=e Tk=e,( T T T为一置换),那么 k k k的最小正整数解为 T T T拆分出所有循环的长度的 L C M LCM LCM
2.设 T k = e T^k=e Tk=e,( T T T为一循环),那么 k k k的最小正整数解为 T T T的长度

整数幂运算

取一 T = [ 1 , 3 , 5 , 2 , 4 , 6 ] T=[1,3,5,2,4,6] T=[1,3,5,2,4,6]
T 2 = ( 1 2 3 4 5 6 5 6 2 1 4 3 ) = [ 1 , 5 , 4 ] ⋅ [ 2 , 6 , 3 ] T^2=\begin{pmatrix} 1 &2 &3 &4 &5 &6 \\ 5 &6 &2 &1 &4 &3 \end{pmatrix}=[1,5,4]\cdot[2,6,3] T2=(152632415463)=[1,5,4][2,6,3]

T 3 = ( 1 2 3 4 5 6 2 1 4 3 6 5 ) = [ 1 , 2 ] ⋅ [ 3 , 4 ] ⋅ [ 5 , 6 ] T^3=\begin{pmatrix} 1 &2 &3 &4 &5 &6 \\ 2 &1 &4 &3 &6 &5 \end{pmatrix}=[1,2]\cdot[3,4]\cdot[5,6] T3=(122134435665)=[1,2][3,4][5,6]

T 4 = ( 1 2 3 4 5 6 4 3 6 5 1 2 ) = [ 1 , 4 , 5 ] ⋅ [ 2 , 3 , 6 ] T^4=\begin{pmatrix} 1 &2 &3 &4 &5 &6 \\ 4 &3 &6 &5 &1 &2 \end{pmatrix}=[1,4,5]\cdot[2,3,6] T4=(142336455162)=[1,4,5][2,3,6]

T 5 = ( 1 2 3 4 5 6 6 5 1 2 3 4 ) = [ 1 , 6 , 4 , 2 , 5 , 3 ] T^5=\begin{pmatrix} 1 &2 &3 &4 &5 &6 \\ 6 &5 &1 &2 &3 &4 \end{pmatrix}=[1,6,4,2,5,3] T5=(162531425364)=[1,6,4,2,5,3]
对一个长度为 l l l的循环 T T T
(1) l l l k k k的倍数,则 T k T^k Tk k k k个循环的乘积,每个循分别是循环 T T T中下标 i i i m o d mod mod k = 0 , 1 , 2... k=0,1,2... k=0,1,2...的元素的顺序连接。
(2) g c d ( l , k ) = 1 gcd(l,k)=1 gcd(l,k)=1,则 T k T^k Tk是一个循环,且与循环 T T T不一定相同

(3) T k T^k Tk g c d ( l , k ) gcd(l,k) gcd(l,k)个循环的乘积,每个循环分别是循环 T T T中下标 i i i m o d mod mod k = 0 , 1 , 2... k=0,1,2... k=0,1,2...的元素的连接

特别的,当循环长度与指数k互质时, a ′ [ i ] = a [ i ∗ k % l ] a'[i]=a[i*k\%l] a[i]=a[ik%l](0-index)
如下例:
T = ( 1 2 3 4 5 4 5 2 3 1 ) T=\begin{pmatrix} 1 &2 &3 &4 &5 \\ 4 &5 &2 &3 &1 \end{pmatrix} T=(1425324351) k = 2 k=2 k=2。对应轮换 a = [ 1 , 4 , 3 , 2 , 5 ] a=[1,4,3,2,5] a=[1,4,3,2,5]
a ′ = [ 1 , 3 , 5 , 4 , 2 ] a'=[1,3,5,4,2] a=[1,3,5,4,2],用 a n s [ i ] = a ′ [ i + 1 ] ans[i]=a'[i+1] ans[i]=a[i+1]还原得 T 2 = ( 1 2 3 4 5 3 1 5 2 4 ) T^2=\begin{pmatrix} 1 &2 &3 &4 &5 \\ 3 &1 &5 &2 &4 \end{pmatrix} T2=(1321354254)

分数幂运算(开方)

给定一个长 l l l置换 T ′ T' T,整数 k k k。求 T T T,满足 T k = T ′ T^k=T' Tk=T

单循环分数幂运算
只有循环长度 l l l与指数 k k k互质时有解,若不互质,则按整数幂运算中的结论, k k k次幂一定会分裂成 g c d ( l , k ) gcd(l,k) gcd(l,k)个循环,而不可能是一个循环。

对循环长度与指数互质得情况,同样的可按照整数幂特例逆向还原。即现由置换转换为轮换,按 a ′ [ i ] = a [ i ∗ k % l ] a'[i]=a[i*k\%l] a[i]=a[ik%l](0-index)进行转换,注意此时是由 a ′ a' a去求 a a a。求出 a a a后再将轮换转换为对应置换即可。

多循环分数幂运算

考虑循环长度与指数不互质的情况,幂次操作后会分裂为 g c d ( l , k ) gcd(l,k) gcd(l,k)个循环,那么对于开方这个逆过程,就可以视为将几个循环合并为 1 1 1个循环。例如可取 k k k个相同长度 l l l的循环,合并会得到一个长 k ∗ l k*l kl的循环,若满足 g c d ( l , k ) = 1 gcd(l,k)=1 gcd(l,k)=1 g c d ( l ∗ k , k ) = k gcd(l*k,k)=k gcd(lk,k)=k, k k k次幂即会分裂为这 k k k个循环。

实际选择时不一定要选 k k k个长度相同的循环进行合并。假设选 m m m个长 l l l的循环,只要保证 m ∗ l m*l ml会将自己分裂成 m m m份即可,即满足 g c d ( m ∗ l , k ) = m , g c d ( l , k / m ) = 1 gcd(m*l,k)=m,gcd(l,k/m)=1 gcd(ml,k)=m,gcd(l,k/m)=1, m m m最小为 g c d ( l , k ) gcd(l,k) gcd(l,k)
对于具体的方法,根据整数幂运算结论 2 2 2,将这几个循环交错合并即可。(可以看一下整数幂所给例子方便理解)

当然对一个循环若长度满足与指数互质也可按单循环分数幂的方法开方。

那么对于一个多循环开方,就有了如下策略:若循环长度不满足用单循环分数幂开方,可找 m m m个相应长度的循环交错合并。若找不到则无法开方。

模板

未标注的复杂度均为 o ( n ) o(n) o(n)

应用一个置换

int tmp[MAX_N];//对排列a进行b置换,n为长度。结果存在a数组中
void Substitution_Group_Op(int *a,int *b,int n)
{
    repi(i,1,n) tmp[i]=a[b[i]];
    repi(i,1,n) a[i]=tmp[i];
}

置换的秩

/*
置换的秩:通过自身置换换回原排列的次数。值为每个轮换长度的lcm。
求解过程可先质因数分解最后统一相乘,需要时用大数运算
*/
int jie[MAX_N/10];
bool vis[MAX_N];
ll Substitution_Group_Rank(int *a,int n)//a为置换,n为其长度
{
	ll ans=1;
//	repi(i,1,cntp) jie[i]=0;
	repi(i,1,n) vis[i]=false;
	repi(i,1,n)if(!vis[i]){
        int t=1,now=i;
        while(a[now]!=i) vis[now]=true,now=a[now],t++; vis[now]=true;
/*      divi.clear(); Prime_Factorization(t);
        for(auto x:divi) jie[pos[x.fi]]=max(jie[pos[x.fi]],x.se);*/
		ans=ans/__gcd(ans,1ll*t)*t;
    } 
//  repi(i,1,cntp)repi(j,1,jie[i]) ans*=prime[i];
    return ans;
}

置换-轮换转换

//置换a转换为循环 存在rec中,rec[i]存的为长i的循环
vector<vector<int> >rec[MAX_N];
bool vis[MAX_N];
void Substitution_Group_Get_Loop(int *a,int n)
{
    repi(i,1,n) vis[i]=false,rec[i].clear();
    repi(i,1,n)if(!vis[i]){
        int t=1,now=i;
        vector<int> tmp;
        while(a[now]!=i) tmp.pb(now),vis[now]=true,now=a[now],t++; vis[now]=true,tmp.pb(now);
        rec[t].pb(tmp);
    }
}
//轮换tmp(下标0开始)转为循环ans
int tmp[MAX_N],ans[MAX_N];
void solve(int *tmp,int n){ repi(i,0,n-1) ans[tmp[i]]=(tmp[(i+1)%n]); }

置换乘法

int tmp[MAX_N];//置换a*置换b,n为长度。结果存在a数组中 
void Substitution_Group_Mul(int *a,int *b,int n)
{
	repi(i,1,n) tmp[i]=b[a[i]];
	repi(i,1,n) a[i]=tmp[i];
}

置换的幂
根据置换的结合律与快速幂的思想实现, o ( n l o g n ) o(nlogn) o(nlogn)

int res[MAX_N];//答案存在这里
void Substitution_Group_Pow(int *a,int n,int m)//置换a,长度n的m次幂
{
	repi(i,1,n) res[i]=i;
	while(m){
		if(m&1) Substitution_Group_Mul(res,a,n);
		Substitution_Group_Mul(a,a,n);
		m>>=1;
	}
}

根据轮换性质实现

//根据置换本身定义可o(n)实现
int res[MAX_N];
vector<int> rec[MAX_N];
int cnt;
bool vis[MAX_N];
void Substitution_Group_Pow(int *a,int n,int m)//m>0
{
	cnt=0;
	repi(i,1,n) vis[i]=false;
	repi(i,1,n)if(!vis[i]){
		int now=i; 
		rec[++cnt].clear();
		while(!vis[now]) rec[cnt].pb(now),vis[now]=true,now=a[now];
	}
	repi(i,1,cnt){
		int len=rec[i].size();
		repi(j,0,len-1) res[rec[i][j]]=a[rec[i][(j+m-1)%len]];
	}
}

置换的分数幂

int tmp[MAX_N],ans[MAX_N];
bool Substitution_Group_Sqrt(int *a,int n,int k)//长n的置换a开k次方,答案存在ans数组中,下标1开始
{
    Substitution_Group_Get_Loop(a,n);
    repi(i,1,n)if(rec[i].size()%__gcd(i,k)) return false;//无解的情况
    repi(l,1,n)if(rec[l].size()){
        int len=rec[l].size();
        if(__gcd(l,k)==1)repi(i,0,len-1){
        	repi(j,0,l-1) tmp[1ll*j*k%l]=rec[l][i][j];
    		repi(j,0,l-1) ans[tmp[j]]=(tmp[(j+1)%l]);
		}
        else{
            int num=__gcd(l,k);
            for(int i=0;i<len-1;i+=num){
                int cnt=0;
                repi(j,0,l-1)repi(t,i,i+num-1) tmp[cnt++]=rec[l][t][j];
                repi(j,0,cnt-1) ans[tmp[j]]=(tmp[(j+1)%cnt]);
            }  
        }
    }
    return true;
}

例题

1.poj1026 给出一个排列 a a a与一个字符串,每次操作 i i i位置的字符去到 a [ i ] a[i] a[i],求操作 x x x次后的字符串
按这个题的操作规则实际是不同于置换的定义的(置换为i位置上的数换为a[i]位置上的数)
但基本思想还是一样的。先求出秩以及每个循环,每个循环内部移位即可。

const int MAX_N=1e3+5;
int a[MAX_N];
bool vis[MAX_N];
char s[MAX_N],ans[MAX_N];
vector<int> rec[MAX_N];
int main()
{
	int n;
	while(si(n)&&n)
	{
		repi(i,1,n) si(a[i]);
		ms(vis);
		int cnt=0;
		repi(i,1,n)if(!vis[i]){
			int tmp=i; rec[++cnt].clear();
			while(!vis[tmp]) vis[tmp]=true,rec[cnt].pb(tmp),tmp=a[tmp];	
		}
		int k;
		while(si(k)&&k)
		{
			getchar(),gets(s+1);
			int len=strlen(s+1);
			repi(i,len+1,n) s[i]=' ';
			s[n+1]='\0';
			repi(i,1,cnt){
				int len=rec[i].size();
				repi(j,0,len-1) ans[rec[i][(j+k)%len]]=s[rec[i][j]];
			}
			ans[n+1]='\0';
			printf("%s\n",ans+1);
		}
		puts("");
	}
	return 0;
}

2.2020牛客暑期多校-5-E 给出一个长度为 n n n的置换,求这个置换的秩模 1 0 n 10^n 10n
1 0 n 10^n 10n实际可忽略,长n的置换的秩一定小于 1 0 n 10^n 10n。按每个循环节长度求 L C M LCM LCM即可。注意 n n n上限达到 1 e 5 1e5 1e5,需要对每个循环节质因数分解后大数计算

int a[MAX_N];
bool vis[MAX_N];
int main()
{
    init();
    repi(i,1,cntp) jie[i]=0;
    int n; si(n);
    repi(i,1,n) si(a[i]),vis[i]=0;
    repi(i,1,n)if(!vis[i]){
        int t=1,now=i;
        while(a[now]!=i) vis[now]=true,now=a[now],t++;
        divi.clear(); Prime_Factorization(t);
        for(auto x:divi) jie[pos[x.fi]]=max(jie[pos[x.fi]],x.se);
    }
      
    BigInt ans(1);
    repi(i,1,cntp)if(jie[i]){
        BigInt tmp(1),mt(prime[i]);
        repi(j,1,jie[i]) tmp=tmp*mt;
        ans=ans*tmp;
    }
    ans.output();
    return 0;
}

3.2020牛客暑期多校-6-J m次操作,每次给出x,k,对原排列进行k次取x位置数的约瑟夫环,求最后排列是什么样
等价于求出约瑟夫环 a a a后对原排列应用置换 a k a^k ak,求约瑟夫环可用树状数组 o ( n l o g n ) o(nlogn) o(nlogn)实现。求这个置换的幂按轮换性质 o ( n ) o(n) o(n)或由类似快速幂的方法 o ( n l o g n ) o(nlogn) o(nlogn)实现。

int ans[MAX_N];
int main()
{
	int n,m; si(n),si(m);
	repi(i,1,n) ans[i]=i;
	while(m--)
	{
		int k,x; si(k),si(x);
		Josephus_trans(n,k);
		Substitution_Group_Pow(a,n,x);
		Substitution_Group_Op(ans,res,n);
	}
	repi(i,1,n) printf("%d%c",ans[i],ce(i,n));
	return 0;
}

4.CodeFoces-612E 对一个置换 P P P开方,若无法开方输出 − 1 -1 1
求出每个循环的长度,对于奇数长度的循环 g c d ( l , 2 ) = 1 gcd(l,2)=1 gcd(l,2)=1,也就是平方后是不分裂的,奇数长度循环直接忽略。而对偶数长度的循环, g c d ( l , k ) = 2 gcd(l,k)=2 gcd(l,k)=2,也就是一定是由一个 2 ∗ l 2*l 2l的循环分裂而来。故对偶数长度的循环,一定存在一个与之长度相等的配对才满足可开方。

int a[MAX_N];
int main()
{
    int n; si(n); k=2;
    repi(i,1,n) si(a[i]);
    if(!Substitution_Group_Sqrt(a,n,2)){ puts("-1"); return 0; }
    else repi(i,1,n) printf("%d%c",ans[i],ce(i,n));
    return 0;
}

5.2020牛客暑期多校-2-J 给定一个置换 B B B,和一个整数 k k k,求置换 A A A,满足 A k = B A^k=B Ak=B
按照 k k k的数据范围,因 k > n k>n k>n, g c d ( k , l ) gcd(k,l) gcd(k,l)一定为 1 1 1,故必有解,直接开方即可。(好像和上面的代码几乎无区别)

int a[MAX_N];
int main()
{
    int n,k; si(n),si(k);
    repi(i,1,n) si(a[i]);
    if(!Substitution_Group_Sqrt(a,n,k)){ puts("-1"); return 0; }
    else repi(i,1,n) printf("%d%c",ans[i],ce(i,n));
    return 0;
}

6.poj 3270 n个互不相同的数,每次操作可将任意两个交换位置,花费为两位值数的和。问将这n个数交换为升序的最小花费
要换到最终顺序,会发现实际也就是几个循环。而要花费最小,每次就拿循环中最小的数去交换。但存在一种可能先从循环外换来一个更小的数再换出去的情况。两种都算一下贪心即可。

const int MAX_N=1e5+5;
ll a[MAX_N],b[MAX_N];
int order[MAX_N];
bool vis[MAX_N];
int main()
{
	int n; si(n);
	repi(i,1,n) sl(a[i]),b[i]=a[i];
	sort(b+1,b+1+n);
	ll mn=b[1];
	repi(i,1,n) order[b[i]]=i;
	ll ans=0;
	repi(i,1,n)if(!vis[i]){
		int now=i,cnt=0;
		ll sum=0,mn2=1e9;
		while(!vis[now]) vis[now]=true,sum+=a[now],mn2=min(mn2,a[now]),cnt++,now=order[a[now]];
		ans+=min(sum-mn2+(cnt-1)*mn2,2*(mn2+mn)+(sum-mn2)+(cnt-1)*mn);
	}
	printf("%lld\n",ans);
	return 0;
} 

7.poj3590 求出长度为n的置换的最大的秩,并构造出一个字典序最小的解
对于最大秩dp处理即可,状态 d p [ i ] [ j ] dp[i][j] dp[i][j]为长 i i i j j j段的最大秩。dp过程中记录路径。构造解时从长度最小的循环开始,如长 3 3 3,就 231 2 3 1 231。后面类推。

const int MAX_N=1e2+5;
int prime[30]={0,2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97},cntp=25;
ll dp[MAX_N][MAX_N];
int pre[MAX_N][MAX_N],mx[MAX_N];
vector<int> rec[MAX_N];
int ans[MAX_N];
int main()
{
	ms(dp),ms(pre);
	repi(i,1,100) dp[i][1]=i,pre[i][1]=i,mx[i]=1;
	repi(i,2,100)repi(j,1,i)for(ll k=1;k<i&&i-k>=j-1;k++)if(dp[i][j]<dp[i-k][j-1]/__gcd(dp[i-k][j-1],k)*k)
		dp[i][j]=dp[i-k][j-1]/__gcd(dp[i-k][j-1],k)*k,pre[i][j]=k;
	repi(i,1,100)repi(j,2,i)if(dp[i][mx[i]]<=dp[i][j]) mx[i]=j;
	repi(i,1,100){
		int ti=i,tj=mx[i];
		while(tj) rec[i].pb(pre[ti][tj]),ti-=pre[ti][tj--];
		sort(all(rec[i]));
	}
	int T; si(T);
	while(T--)
	{
		int n; si(n);
		printf("%lld ",dp[n][mx[n]]);
		ans[0]=0;
		int now=1,len=rec[n].size();
		repi(i,0,len-1){
			repi(j,1,rec[n][i]-1) ans[++ans[0]]=now+j;
			ans[++ans[0]]=now,now+=rec[n][i];
		}
		repi(i,1,n) printf("%d%c",ans[i],ce(i,n));
	}
	return 0;
} 

完结撒花

你可能感兴趣的:(组合数学)