花式全排列,不服不行(康拓展开)

偶尔做了些蓝桥杯练习题,发现一题全排列,看着简单以为C++全排列库直接暴力就行,然而……答案数据高达229526多亿

基础秀

#include <iostream>
#include <algorithm>   
using namespace std;
int main () {
    char dp[] = "abcd";
    do{
        cout<<dp[0]<<" "<<dp[1]<<" "<<dp[2]<<" "<<dp[3]<<endl; 
    }while( next_permutation(dp,dp+4) );  
    return 0;
}

我们来看看这短短10行代码的输出结果

a b c d
a b d c
a c b d
a c d b
a d b c
a d c b
b a c d
b a d c
b c a d
b c d a
b d a c
b d c a
c a b d
c a d b
c b a d
c b d a
c d a b
c d b a
d a b c
d a c b
d b a c
d b c a
d c a b
d c b a

是不是感觉很棒?短短10行代码就实现了全排列……那我要输出……比如abcd开始的第8个是什么,要怎么实现?

#include <iostream>
#include <algorithm>   
using namespace std;
int main () {
    char dp[] = "abcd";
    int temp = 0;
    do{
        temp++;
        if(temp == 8) cout<<dp[0]<<" "<<dp[1]<<" "<<dp[2]<<" "<<dp[3]<<endl;
    }while( next_permutation(dp,dp+4) );  
    return 0;
}

的确,加一个计数器不就好了,哪里难了。那……请问如果是从abcdefghijklmnopq开始,然后要求第22952601027516全排列数是什么?你怎么求???的确,搏心态试一试,我试了10亿,也就是1000000000,用了27秒。估算了一下,22952601027516大概需要7天才能算出来……

花式秀

首先把那题练习题拿出来。

X星系的某次考古活动发现了史前智能痕迹。
这是一些用来计数的符号,经过分析它的计数规律如下:
(为了表示方便,我们把这些奇怪的符号用a~q代替)

abcdefghijklmnopq 表示0
abcdefghijklmnoqp 表示1
abcdefghijklmnpoq 表示2
abcdefghijklmnpqo 表示3
abcdefghijklmnqop 表示4
abcdefghijklmnqpo 表示5
abcdefghijklmonpq 表示6
abcdefghijklmonqp 表示7
.....

在一处石头上刻的符号是:
bckfqlajhemgiodnp

请你计算出它表示的数字是多少?

也就是,给你第n个数的全排列方式,让你求这个数n是多少。废话不多说,直接上代码。

#include <iostream>
#include <string>
using namespace std;  
int main() {  
    int flag[113] = {0};
    string ss="bckfqlajhemgiodnp";  
    long long int sum=0;  
    long long int ans[18]; 
    ans[0]=1;  
    for(int i=1;i<=16;i++) 
        ans[i]=ans[i-1]*i;  
    for(int i=0;i<=16;i++) {  
        for(int j='a';j<ss[i];j++)
            if(flag[j]==0)   
                sum+=ans[16-i];
        flag[ss[i]] = 1; 
    }  
    cout<<sum<<endl;  
    return 0;  
}  

写了这么久之后忽然回过来发现,这个算法是,康拓展开,而下面的,则是康拓展开的逆运算。原理就是通过将一个全排列压缩这个一个X进行存储,从而达到压缩空间的效果

解析一波
  • 这段代码的主要思想其实就是模拟。首先用ans来标记该字符串的第n个位置的权值。即第n位如果要变动,每变动一个都需要将位数增加(n-1)!

  • 例如:abcd->dcba,第3位(从右往左)本来是a,然后变成了d,途经了a->b->c->d。所以,sum += 3×3! 。第2位本来是a,然后变成了c。途经了a->b->c,所以,sum += 2×2!。第1位本来是a,然后变成了b,需要途经a->b,所以sum += 1!。第0位是 a不变,所以结果是6×3+2×2+1 = 23

  • 这里需要注意的是,如果abcd->bdca,第3位是sum += 1×3!,但是第2位,a->d中途经的是a->c->d,因为b在之前已经被使用过了,所以不被途经。

OK,那判断这个字符串是第几个全排列的我知道了,如果我已知的是n,需要得到的是全排列的字符串呢?
#include <iostream>
#include <string>
using namespace std;  
int main() {  
    long long int ans[18];
    long long int num;
    ans[0]=1;  
    for(int i=1;i<=18;i++) 
        ans[i]=ans[i-1]*i; 
    while(cin>>num){
        string ss="abcdefghijklmnopq";
        int flag[200] = {0};
        int len = ss.size();  
        for(int i=len; i>=1; i--){
            char ch = 'a';
            while(flag[ch]==1) ch += 1;
            while(num>=ans[i-1]){
                ch += 1;
                while(flag[ch]==1) ch += 1;
                num -= ans[i-1];
            }
            ss[len-i] = ch;
            flag[ch] = 1;
        }
        cout<<ss<<endl;
    }
    return 0;  
}  

把代码稍微改编下就能达到要求了,但是需要注意long long int的最大值是9223372036854775807。

相似题目扩展

在题库中,不止有这些康拓展开的裸题,还有一些类似康拓展开的题目。比如HDU2062

这题也是类似的使用康拓展开的思想,把数组每个下标对应的位置赋予不同的权值,这题不同的在于,权值由每个每个n对应的分组表示,具体代码与注释如下:

#include <iostream>
#include <string.h>
using namespace std;
long long int ans[22],m;
int main(int argc, const char * argv[]) {
    int n,t,tmp,cot = 0,flag[22];
    ans[0] = 0;
    for (int i=1; i<21; i++)  //标记每个分组的权值
        ans[i] = (i-1)*ans[i-1] + 1;
    while (cin>>n>>m) { //注意m是long long int型的
        memset(flag, 0, sizeof(flag));
        while (m&&n) {
            t = (m-1)/ans[n];  //取得对应的分组编号,t+1为分组编号
            tmp = t+1;  //缓存数据,用来找第一个没使用过得第tmp大小的数
            for (int i=1; i<=20; i++) {   //找符合条件的数
                if (tmp==1 && flag[i]==0) {
                    cot = i;
                    break;
                } else if (flag[i]==0) tmp--;
            }
            flag[cot] = 1;  //标记已经使用过
            cout<<cot; //输出数据
            m = m - ans[n]*t - 1; //新的m值需要减去前面的所有组数和本组的一个空组
            n--;
            if (n&&m) cout<<" ";  //空格要求
        }
        cout<<endl;
    }
    return 0;
}

最后留下一个小常识,在全局变量中定义的int数组的默认值都是0,而在主函数中定义的int数组的值是随机的。试试如下代码

#include <iostream>
using namespace std;
int a[10];    //作为全局变量
int main(int argc, char *argv[]){
    //int a[10];    在主函数中定义
    for(int i=0; i<10; i++)
        cout<<a[i]<<" ";
    cout<<endl;
    return 0;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容