减治求有重复元素的全排列

求n个元素的全排列的所有解可以用减治法:每次拎出一个数做前缀,对剩下的元素再求全排列,直至只剩一个元素。代码源自《算法分析与设计(王晓东)》,复杂度O(n2)

 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 }

以上没有考虑有重复元素的情况。简单地想,若有元素重复,则这个元素只需被拎出来做一次前缀就好了,那么只需在拎前缀前判断这个数是否已被拎出来过。

那么如何高效地判断呢?可以先对所有元素排序,这样重复元素分布在相邻位置,一趟扫描,只对与前驱不同的元素做处理。

代码只需在k~m的循环内加一句 if(i>k&&list[i]==list[i-1]) continue;

下面再来看一道类似的问题

UVA11076  http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=33478

由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 <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];//出现的次数
10 int cnt_2[10];//在所有全排列的任一列中出现的次数
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后无重复全排列的个数
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 }

注:之前自己把自己搞晕过,去重实现不了当成是swap的问题,认为简单交换i与k会破坏“重复元素集中分布”或“有序序列”这两个条件,但再分析发现并没什么关系。。。减治啊减治,前缀被拎走就对后缀的全排列没影响了,只要保证每次循环中重复元素不被拎到同一位置就可以。

从问题本身和算法思想出发去分析还是很有意思的~算法知识博大精深,希望自己多练习多积累,早日不再那么水~~~

时间: 2024-10-06 00:29:10

减治求有重复元素的全排列的相关文章

九章算法面试题54 带重复元素的全排列

九章算法官网-原文网址 http://www.jiuzhang.com/problem/54/ 题目 给定一个带重复元素的整数集合,求出这个集合中所有元素的全排列.对于集合[1,1,2],其本质不同的全排列有三个,分别为: [1,1,2] [1,2,1] [2,1,1] 在线测试本题 http://lintcode.com/problem/unique-permutations/ 解答 首先做这个题目之前,要先会不带重复元素的全排列. 程序参考:http://www.ninechapter.co

poj3421 X-factor Chains(重复元素的全排列)

poj3421 X-factor Chains 题意:给定正整数$x(x<=2^{20})$,求$x$的因子组成的满足任意前一项都能整除后一项的序列的最大长度,以及满足最大长度的子序列的个数. 显然最大长度就是$x$的质因数个数(一个一个加上去鸭) 而满足最大长度的子序列个数.... 这不就是可重复元素的全排列吗! 有这么一个公式,设元素总个数$n$,每个重复元素的个数$m_{i}$,共$k$种不同元素 则全排列个数$=\frac{n!}{\prod_{i=1}^{k}m_{i}!}$ 发现$n

Concise and clear CodeForces - 991F(dfs 有重复元素的全排列)

就是有重复元素的全排列 #include <bits/stdc++.h> #define mem(a, b) memset(a, b, sizeof(a)) using namespace std; typedef long long LL; const int maxn = 10010, INF = 0x7fffffff; char str[maxn]; int vis[maxn], v[maxn]; LL num[maxn]; LL res = 0; void init() { num[0

递归法求不重复字符串的全排列

我们可以将这个排列问题画成图形表示,即排列枚举树,比如下图为{1,2,3}的排列枚举树,此树和我们这里介绍的算法完全一致: 算法思路: (1)n个元素的全排列=(n-1个元素的全排列)+(另一个元素作为前缀): (2)出口:如果只有一个元素的全排列,则说明已经排完,则输出数组: (3)不断将每个元素放作第一个元素,然后将这个元素作为前缀,并将其余元素继续全排列,等到出口,出口出去后还需要还原数组: 引用自:http://blog.csdn.net/xiazdong/article/details

有重复元素的全排列

问题: 有k个元素,其中第i个元素有ni个,求全排列个数 分析: 令所有ni之和为n,设答案为x 首先做全排列, 然后把所有元素编号 其中第s中元素编号为1~ns 由于编号后所有元素均不相同,方案总数为n的全排列数n! n1!n2!n3!...nk!x=n! 移项即可 原文地址:https://www.cnblogs.com/darlingroot/p/10388821.html

刷题——有重复元素的全排列(Permutations II)

题目如上所示. 我的解决方法(参考了九章的答案!): class Solution { public: /* * @param : A list of integers * @return: A list of unique permutations */ vector<vector<int>> permuteUnique(vector<int> &nums) { vector<vector<int>> results; vector&l

Add Again(重复元素排序)

Add Again Input: Standard Input Output: Standard Output Summation of sequence of integers is always a common problem in Computer Science. Rather than computing blindly, some intelligent techniques make the task simpler. Here you have to find the summ

求字符串中元素的所有组合

我们知道具有N个元素的数字进行组合,总共有2^N种情况.那么,如何用程序实现输出这些组合呢???这个问题似乎比 求N个元素进行全排列 的问题要复杂一些,大家可以动脑想一下或者动手去写写程序,那么该采取什么样的思路才是简单的呢??? 常规的思路我们会想到使用循环或者递归,但是实际动手操作起来非常复杂,经常会把自己绕晕,或者很难看懂别人写的程序.下面介绍一种使用不同思路实现的方法,简单有效: 首先,把字符数组每一个元素用一个二进位表示,例如: A B C D E 1 1 1 1 1 ---> 于是它

从n个元素中选择k个的所有组合(包含重复元素)

LeetCode:Combinations这篇博客中给出了不包含重复元素求组合的5种解法.我们在这些解法的基础上修改以支持包含重复元素的情况.对于这种情况,首先肯定要对数组排序,以下不再强调 修改算法1:按照求包含重复元素集合子集的方法LeetCode:Subsets II算法1的解释,我们知道:若当前处理的元素如果在前面出现过m次,那么只有当前组合中包含m个该元素时,才把当前元素加入组合 ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20