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


    求n个元素的全排列的所有解可以用减治法:每次拎出一个数做前缀,对剩下的元素再求全排列,直至只剩一个元素。代码源自《算法分析与设计(王晓东)》,复杂度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("
    ");
     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
    ",ans);
    46     }
    47     return 0;
    48 }

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

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

  • 相关阅读:
    带色彩恢复的多尺度视网膜增强算法(MSRCR)的原理、实现及应用。
    索引图像的那些事啊
    调整图像 自动对比度、自动色阶算法
    图像处理界双线性插值算法的优化
    共享收集的图像处理方面的一些资源和网站。
    Wellner 自适应阈值二值化算法
    基于灰度世界、完美反射、动态阈值等图像自动白平衡算法的原理、实现及效果
    VB6.0用GDI+保存图像为BMP\JPG\PNG\GIF格式终结版。
    关于.net中获取图像缩略图的函数GetThumbnailImage的一些认识。
    限制对比度自适应直方图均衡化算法原理、实现及效果
  • 原文地址:https://www.cnblogs.com/helenawang/p/4862782.html
Copyright © 2020-2023  润新知