题目地址:http://acm.hdu.edu.cn/game/entry/problem/show.php?chapterid=1§ionid=3&problemid=17
这题的核心算法就是排列问题:
就目前常用的排列算法有两种:
一种是按字典列出排序,C++ STL所使用的方法,能够支持重复元素的全排列。
另外一种是使用递归生成排序。
先说容易理解的一种方法,使用递归生成排序:
例如1,2,3,4,这个序列。
最开始,也就是递归最外层我们可以将其分成:
1 2,3,4,
2 1,3,4
3 1,2,4
4 1,2,3
这四个子分组:
然后递归到某一个子分组当中比如2,3,4
于是就可以得到:
2 3,4
3 2,4
4 2,3
这三个子分组。
再次递归进入某一个分组:
3 4
4 3
当只有第一个数的时候,便不可分了,打印该排列。
于是递归的算法可以写成:
perm(int *list,int l,int r){ if(l>r){//递归结束条件 for(int i=0;i<4;i++){ printf("%d ",list[i]); } break; }else{ for(int i=l;i<=r;i++){ swap(&list[i],&list[l]);//传递引用,否则交换失败 perm(list,l+1,r); swap(&list[i],&list[l]);//交换回来,保证原数组不变 } } }
按照字典列出排序的方法:
它的思想就是不断的找到一个排列中的下一个排列,而且这两个排列之间不会再有任何排列,整个排列都是从小到大排序的,
比如一个任意排列:134987652,我们如何确定下一个排列是多少呢?
从右往左看,第一个小于它的右边的数是4,,13(4)987652。
然后从4右边的数字开始,我们找到最后一个大于它的数字5,1349876(5)2.。
然后调换4,5,得到数字串:13(5)9876(4)2.
翻转5 后面的序列我们得到:13(5)2(4)6789
我们获得的这个序列就是134987652的下一个排列的数字。
为什么呢?
我们仔细研究就会发现:
在第一步从右往左寻找第一个比他右边小的数字的时候,就是确定最后一个递减子序列的位置,直接将其翻转就是该子序列的字典最小值,我们在翻转之前需要变化递减子序列的前一个数字也就是4,[13(4)987652]。
确定该子序列的前一个数,我们将递减子序列当中的大于该数的最小数与它交换,在例子中也就是4和5交换,13(5)9876(4)2,于是我们可以知道,调换以后,递减子序列9876(4)2依然保持递减的特性,而递减子序列之前的数字增大,递减子序列之前的序列13(5)在字典序上恰好增大到下一个字典子序列,这时,我们需要递减子序列的最小字典子序列。
我们翻转递减子序列,得到递减子序列的最小字典序987652,于是就得到了整个序列的递增的下一个字典序列。
字典实现排列算法的代码如下:
bool next_perm(int *list,int l,int r){ int k=r; while(k>l&&list[k-1]>=list[k])k--; if(k==l){ //已经不存在递增部分,此时已经到达最后一个数字。 return false; } int j=k;//取 k-1 while(j<=r&&list[j]>list[k-1])j++; //取j-1 swap(list[k-1],list[j-1]);//交换list[k-1],list[j-1] inverse(list,k,r); for(int i=l;i<=r;i++){ printf("%d ",list[i]); } printf("\n"); return true; }
说了这么多,回到排列2这道题目上,我的ac代码如下:
#include<iostream> #include<cstdio> #include<cstdlib> #include<algorithm> #include<set> using namespace std; set<int> s; int cnt=0; void init(){ cnt=0; s.clear(); } void swap(int *a,int *b){ int m=*a; *a = *b; *b = m; } //递归算法 void perm(int *list, int l,int r){ if(l>r){ int v=0; for(int i=0;i<4;i++){ v=v*10+list[i]; } s.insert(v); }else{ for(int i=l;i<=r;i++){ swap(&list[l],&list[i]); perm(list,l+1,r); swap(&list[l],&list[i]); } } } int main(){ int list[4]; bool first=true; while(scanf("%d %d %d %d",&list[0],&list[1],&list[2],&list[3])==4){ if(list[0]==0&&list[1]==0&&list[2]==0&&list[3]==0){ break; } if(!first)printf("\n"); first = false; init(); perm(list,0,3); set<int>::iterator iter; int header = -1; for(iter=s.begin();iter!=s.end();iter++){ int value = *iter; if(value>=1000){ if(header==-1){ header = value/1000; printf("%d",value); continue; } if(header==value/1000){ printf(" %d",value); continue; } if(header!=value/1000&&header!=-1){ header = value/1000; printf("\n%d",value); continue; } } } printf("\n"); } return 0; }