置换群
看了几天置换群,一直没搞清楚定义是怎么回事,一个置换可以写成若干循环的乘积,那么如果置换求幂的话,一个循环不会跑到另一个循环里面去。
我们可以简单理解为这几个位置的数来回换。
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; }
posted on 2011-11-15 12:07 buptLizer 阅读(7557) 评论(0) 编辑 收藏 举报