求n個元素的全排列的所有解可以用減治法:每次拎出乙個數做字首,對剩下的元素再求全排列,直至只剩乙個元素。**源自《演算法分析與設計(王曉東)》,複雜度o(n!)
1//輸出k~m的所有全排列
2void perm(int k,intm)3
else
1017
}18 }
以上沒有考慮有重複元素的情況。簡單地想,若有元素重複,則這個元素只需被拎出來做一次字首就好了,那麼只需在拎字首前判斷這個數是否已被拎出來過。
那麼如何高效地判斷呢?可以先對所有元素排序,這樣重複元素分布在相鄰位置,一趟掃瞄,只對與前驅不同的元素做處理。
**只需在k~m的迴圈內加一句 if(i>k&&list[i]==list[i-1]) continue;
下面再來看一道類似的問題
uva11076
由0~9中n個數字(可重複)組成的陣列,把每個全排列看成乙個n位數,求所有全排列的和。
根據全排列的性質,將所有全排列按行寫出後,發現每一列「所有數字的和s」都相等,因此我們可以只求任一列的和然後進行n次的*10累加。
對於每個數字k,我們已知它在陣列**現的次數cnt[k](即k有cnt[k]-1個副本),但要對一列求和(不妨求第1列),我們需要知道每個數字在這一列出現的次數cnt_2[k]。由於全排列是沒有相同的,那麼「第1位是k」的次數就等價於「去掉k後剩餘元素的全排列的個數」,至此,問題轉化為上面的減治法求全排列。
本題只需求全排列個數(值)而不必輸出具體排列(解),因此可以用高中排列組合的經典做法:先視為無重複全排,再除以所有重複元素的排列個數。
做完發現此題由於數字是0~9所以天然地把元素排好序並記錄好重複次數了,因此對每個數字k只計算一次,計算時將k的個數看作cnt[k]-1即可。
**如下:
1 #include 2 #include 3using
namespace
std;
45 typedef unsigned long
long
ull;6//
只有0~9
7 ull fac=;
8int a[13];9
int cnt[10];//
出現的次數
10int cnt_2[10];//
在所有全排列的任一列**現的次數
11ull sum,ans,s;
12int
n;13
14int
main()
1526 s=0;27
for(int i=0;i<=9;i++)
28
37 s+=i*cnt_2[i];38}
39 ans=0;40
for(int i=0;i)
4145 printf("
%llu\n
",ans);46}
47return0;
48 }
注:之前自己把自己搞暈過,去重實現不了當成是swap的問題,認為簡單交換i與k會破壞「重複元素集中分布」或「有序序列」這兩個條件,但再分析發現並沒什麼關係。。。減治啊減治,字首被拎走就對字尾的全排列沒影響了,只要保證每次迴圈中重複元素不被拎到同一位置就可以。
從問題本身和演算法思想出發去分析還是很有意思的~演算法知識博大精深,希望自己多練習多積累,早日不再那麼水~~~
有重複元素的全排列
題目描述 集合s中有n個元素,其中的元素可能重複,設計乙個演算法,計算出s的不同排列字元全部由小寫字母組成,輸出按照字典序輸出 n 9輸入 第一行乙個整數n 第二行乙個字串包含n個字母輸出 所有的全排列 最後一行輸出個數 樣例輸入 4 aacc 樣例輸出 aacc acac acca caac ca...
有重複元素的全排列
題目描述 description 輸入n 10 個小些字母 可能重複 輸出n個字元的全部排列。input abaab output 1 aaabb 2 aabab 3 aabba 4 abaab 5 ababa 6 abbaa 7 baaab 8 baaba 9 babaa 10 bbaaa 演算法...
全排列 不含重複元素
總結定義 從n個不同元素中任取m m n 個元素,按照一定的順序排列起來,叫做從n個不同元素中取出m個元素的乙個排列。當m n時所有的排列情況叫全排列。示例 對陣列或者字串進行全排列時,一般要求得出所有的排列結果。排列結果中的每個元素來自於原始陣列,數量和內容與原始陣列相同,只是元素的位置發生了改變...