减治求有重复元素的全排列
求n個元素的全排列的所有解可以用減治法:每次拎出一個數(shù)做前綴,對剩下的元素再求全排列,直至只剩一個元素。代碼源自《算法分析與設(shè)計(王曉東)》,復(fù)雜度O(n!)
1 //輸出k~m的所有全排列 2 void perm(int k,int m) 3 { 4 if(k==m) 5 { 6 for(int i=0;i<=m;i++) 7 printf("%d ", list[i]); 8 printf("\n"); 9 }else 10 { 11 for(int i=k;i<=m;i++) 12 { 13 swap(list[k],list[i]); 14 perm(k+1,m); 15 swap(list[k],list[i]); 16 } 17 } 18 }以上沒有考慮有重復(fù)元素的情況。簡單地想,若有元素重復(fù),則這個元素只需被拎出來做一次前綴就好了,那么只需在拎前綴前判斷這個數(shù)是否已被拎出來過。
那么如何高效地判斷呢?可以先對所有元素排序,這樣重復(fù)元素分布在相鄰位置,一趟掃描,只對與前驅(qū)不同的元素做處理。
代碼只需在k~m的循環(huán)內(nèi)加一句?if(i>k&&list[i]==list[i-1]) continue;
?
下面再來看一道類似的問題
UVA11076 ?http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=33478
由0~9中n個數(shù)字(可重復(fù))組成的數(shù)組,把每個全排列看成一個n位數(shù),求所有全排列的和。
根據(jù)全排列的性質(zhì),將所有全排列按行寫出后,發(fā)現(xiàn)每一列“所有數(shù)字的和s”都相等,因此我們可以只求任一列的和然后進行n次的*10累加。
對于每個數(shù)字k,我們已知它在數(shù)組中出現(xiàn)的次數(shù)cnt[k](即k有cnt[k]-1個副本),但要對一列求和(不妨求第1列),我們需要知道每個數(shù)字在這一列出現(xiàn)的次數(shù)cnt_2[k]。由于全排列是沒有相同的,那么“第1位是k”的次數(shù)就等價于“去掉k后剩余元素的全排列的個數(shù)”,至此,問題轉(zhuǎn)化為上面的減治法求全排列。
本題只需求全排列個數(shù)(值)而不必輸出具體排列(解),因此可以用高中排列組合的經(jīng)典做法:先視為無重復(fù)全排,再除以所有重復(fù)元素的排列個數(shù)。
做完發(fā)現(xiàn)此題由于數(shù)字是0~9所以天然地把元素排好序并記錄好重復(fù)次數(shù)了,因此對每個數(shù)字k只計算一次,計算時將k的個數(shù)看作cnt[k]-1即可。
代碼如下:
1 #include <cstdio> 2 #include <cstring> 3 using namespace std; 4 5 typedef unsigned long long ULL; 6 //只有0~9 7 ULL fac[]={1,1,2,6,24,120,720,5040,40320,362880,3628800,39916800,479001600}; 8 int a[13]; 9 int cnt[10];//出現(xiàn)的次數(shù) 10 int cnt_2[10];//在所有全排列的任一列中出現(xiàn)的次數(shù) 11 ULL sum,ans,s; 12 int n; 13 14 int main() 15 { 16 while(scanf("%d",&n)&&n) 17 { 18 memset(cnt,0,sizeof(cnt)); 19 sum=0; 20 for(int i=0;i<n;i++) 21 { 22 scanf("%d",&a[i]); 23 sum+=a[i]; 24 cnt[a[i]]++; 25 } 26 s=0; 27 for(int i=0;i<=9;i++) 28 {//cnt_2[i]等于去掉一個i后無重復(fù)全排列的個數(shù) 29 if(cnt[i]==0) continue; 30 cnt_2[i]=fac[n-1]; 31 for(int j=0;j<=9;j++) 32 { 33 if(cnt[i]==0) continue; 34 if(i==j) cnt_2[i]/=fac[cnt[i]-1]; 35 else cnt_2[i]/=fac[cnt[j]]; 36 } 37 s+=i*cnt_2[i]; 38 } 39 ans=0; 40 for(int i=0;i<n;i++) 41 { 42 ans+=s; 43 s*=10; 44 } 45 printf("%llu\n",ans); 46 } 47 return 0; 48 }注:之前自己把自己搞暈過,去重實現(xiàn)不了當(dāng)成是swap的問題,認(rèn)為簡單交換i與k會破壞“重復(fù)元素集中分布”或“有序序列”這兩個條件,但再分析發(fā)現(xiàn)并沒什么關(guān)系。。。減治啊減治,前綴被拎走就對后綴的全排列沒影響了,只要保證每次循環(huán)中重復(fù)元素不被拎到同一位置就可以。
從問題本身和算法思想出發(fā)去分析還是很有意思的~算法知識博大精深,希望自己多練習(xí)多積累,早日不再那么水~~~
總結(jié)
以上是生活随笔為你收集整理的减治求有重复元素的全排列的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Javascript中Base64编码解
- 下一篇: 输出一个数的二进制序列中1的个数(三种方