置换群
看了几天置换群,一直没搞清楚定义是怎么回事,一个置换可以写成若干循环的乘积,那么如果置换求幂的话,一个循环不会跑到另一个循环里面去。
我们可以简单理解为这几个位置的数来回换。
poj3270
给出一列数,求将这列数排成升序的最小花费,这里花费定义为交换两个数的和。
例如给出一排数8 4 5 3 2 7,那么我们最终的状态为2 3 4 5 7 8,这里的轮换有(8 2 7)(4 5 3),这里8应该在第六个位置,
而第6个位置是7,7应该在5这个位置,而第5个位置为2,2应该在1这个位置,这样就到了8所在的位置,我们称这是一个轮换。
首先需要明确的一点是对于每个群,假设有k个数,那么我们需要交换k-1次得到升序。
对于每一个群,我们有两种换发:
1.群里换,拿群里最小的数t与其他每个数交换,共k-1次,花费为:sum+(k-2)*t.
2.将这个数列最小的数m,拉入这个群,与该群最小的数t交换,然后用这个最小的数与其他数交换k-1次,然后再将m与t换回来,这样
花费为:sum+t+(k+1)m
那么最小花费我们取两者中最小的,即sum+min{(k-2)*t,t+(k+1)*m}.
对于这个题关键就是怎么确定每个群,我们利用计数排序,得到每个数应该在的位置,然后循环判断这个位置是否已经被访问了。时间
复杂度为O(n)
代码:
#include <iostream>
#include <stdio.h>
using namespace std;
const int N=100001;
const int INF=100000000;
int m[N],cnt[N+1];
bool used[N+1];
int main()
{
int n,i,j,tmp1,tmp2,len,t,maxValue,minValue,sum=0,curSum;
memset(cnt,0,sizeof(cnt));
memset(used,false,sizeof(used));
minValue=INF;
maxValue=0;
scanf("%d",&n);
for(i=1;i<=n;++i)
{
cin>>m[i];
cnt[m[i]]++;
if(m[i]<minValue)
minValue=m[i];
if(m[i]>maxValue)
maxValue=m[i];
}
//计数,得到每个数的位置,我们按照上面的那排数,得到的结果应该为6 3 4 2 1 5
for(i=1;i<=maxValue;++i)
cnt[i]=cnt[i]+cnt[i-1];
/* for(i=1;i<=n;++i)
cout<<cnt[m[i]]<<" ";
cout<<endl;
*/
for(i=1;i<=n;++i)
{
if(!used[i])
{
j=i;
t=m[i];
len=0;
curSum=0;
//首先1这个位置未访问,得到8这个数,找到8应该在的位置,是6,6这个位置未访问,得到数7,。。。依次类推
while(!used[j])
{
++len;
if(m[j]<t)
t=m[j];
curSum+=m[j];
used[j]=true;
j=cnt[m[j]];
}
if(1 < len)
sum+=curSum;
if(2<len)
{
tmp1=(len-2)*t;
tmp2=(len+1)*minValue+t;
if(tmp1 > tmp2)
tmp1=tmp2;
sum=sum+tmp1;
}
}
}
cout<<sum<<endl;
return 0;
}
poj2369
给出1-n的一个排列,a1,a2,...,an,表示P(1)=a1,P(2)=a2,...,P(n)=an,P(P(1))=P(a1),P(P(2))=P(a2),
...,P(P(n))=P(an).问经过多少次后使得P(1)=1,...,P(n)=n.
这个是置换群的概念题,找到每个循环节,确定其长度len1,len2,...,lenk,求他们的最小公倍数。
对于题目中给定的一个例子进行分析:
1 2 3 4 5
4 1 5 2 3
上面定义了函数P,那么我们可以看出这个置换可以写成(1 4 2)(3 5),这样循环1中的数绝对不会跑到第二个循环中,每个循环i需要经过leni次后就可以到达目的状态,所以我们只需要确定各个循环节长度的最小公倍数。
poj1026
首先给出一个置换,然后给出一个字符串,问置换k次之后得到的字符串是什么?
我们求出来子循环,然后对每个子循环计算k次之后置换群变成什么排列,用b[0],b[1],...,b[t-1]表示一个子群,那么长度为t,经过一次置换后变成b[0]=b[1],b[1]=b[2],..,b[t-1]=b[0],所以经过k次后变成
b[(0+k)%t],b[(1+k)%t],..,b[(t-1+k)%t],即b[i]->b[(i+k)%t].
代码:
#include <iostream>
#include <stdio.h>
using namespace std;
const int N=201;
int a[N],b[N],c[N];
bool visit[N];
char message[N],rs[N];
int main()
{
int n,k,i,j,t;
while(1)
{
cin>>n;
if(!n)
break;
for(i=0;i<n;++i)
{
cin>>a[i];
--a[i];
}
while(cin>>k)
{
if(!k)
break;
memset(message,'\0',sizeof(message));
memset(rs,'\0',sizeof(rs));
memset(visit,0,sizeof(visit));
getchar();
cin.getline(message,N,'\n');
int len=strlen(message);
if(len<N)
{
for(i=len;i<n;++i)
message[i]=' ';
}
for(i=0;i<n;++i)
{
j=i;
t=0;
if(!visit[j])
{
while(!visit[j])
{
visit[j]=true;
b[t]=j;
t++;
j=a[j];
}
// for(j=0;j<t;++j)
// cout<<b[j]<<" ";
// cout<<endl;
for(j=0;j<t;++j)
{
// cout<<b[j]<<" "<<b[(j+k)%t]<<endl;
rs[b[(j+k)%t]]=message[b[j]];
}
}
}
cout<<rs<<endl;
}
cout<<endl;
}
return 0;
}
poj1721
洗牌机
给出洗牌规则,如果位置I上的牌是J,而且位置J上的牌是K,那么通过洗牌机后位置I上的牌将是K。
首先给出初始顺序a1,a2,...,an,在位置ai处放置ai+1,得到初始序列为x1,x2,...,xn,经过s次洗牌之后,得到新的序列p1,p2,...,pn,现在给出最终序列即s,让我们求xi。这个完全可以用模拟来做,但是时间复杂度可能会高,我们可以求出循环节t,那么s=s%t之后,我们在进行洗n-s次就可以了。
#include <iostream>
using namespace std;
const int N=1001;
int a[N],b[N],m[N];
int main()
{
int i,n,s,j,t;
cin>>n>>s;
for(i=1;i<=n;++i)
{
scanf("%d",&a[i]);
m[i]=a[i];
}
// memset(used,0,sizeof(used));
//找到循环节
t=0;
bool flag;
while(1)
{
for(i=1;i<=n;++i)
b[i]=a[a[i]];
++t;
flag=true;
for(i=1;i<=n;++i)
{
if(b[i]!=m[i])
{
flag=false;
break;
}
}
if(flag)
break;
for(i=1;i<=n;++i)
a[i]=b[i];
}
s=s%t;
t=t-s;
for(i=1;i<=n;++i)
a[i]=b[i];
while(t--)
{
for(i=1;i<=n;++i)
b[i]=a[a[i]];
for(i=1;i<=n;++i)
a[i]=b[i];
}
for(j=1;j<=n;++j)
printf("%d\n",b[j]);
return 0;
}
浙公网安备 33010602011771号