浅谈康托展开和其逆运算

康托展开,是一种在\(\mathcal{O}(n^2)\)(\(n\)为排列元素个数)时间复杂度求解某一排列在全排列中的次序的算法。

我们以一道例题引入:

排列的序号

题目描述:

给定一个数\(n\)和一个\(n\)个数的排列\(a\),求\(a\)在\(n\)的全排列中的序号。

输入描述:

第一行一个整数\(n\),第二行一个排列\(a\)。

输出描述:

求\(a\)在\(n\)的全排列中的序号。

输入输出样例:

输入
3
123
输出

1

数据范围

\(n\le 15\)

根据排列组合、加法原理等等,得出一个式子:
\[ans=\sum_{i=1}^n(a_{n+1-i}(n-i)!)\]
(\(a_i\)表示原数的第\(i\)位在当前未出现的元素中是排在第几个)

此为康托展开,代码如下:

ull Cantor(int n,int a[15])                       //对于n的一个排列a进行康托展开
{
    ull ans=0;                                    //因答案可能很大所以用ull
    for (int i=0;i<n;i++)
    {
        int x=0;                                  //x代指公式中a[i],节省空间
        for(int j=i+1;j<n;j++)                    //计算公式中a[i]
            if (a[j]<a[i]) ++x;
        ans+=x*fact[n-i-1];
    }
    return ans+1;                                 //答案要+1
}

逆康托展开倒着回去就行:

void CantorReverse(long long r,int len,int a[])    //康托展开逆运算,结果在a中
{
    r--;                                           //初始r要减1
    bool vis[20]={0};                              //vis[i]用来标记是否排列中有数字i
    for(int i=1;i<=len;i++)
    {
        long long tp=r/fact[len-i];                //得出商,确定初始值
        r-=tp*fact[len-i];                         //用减法代替取模加快运算
        int j;
        for(j=0;j<len;j++)                         //求出i位上数字
            if(!vis[j]){if(!tp) break;--tp;}       //依次检查vis并标记tp
        vis[j]=1;
        a[i-1]=j;
    }
}

++++++++++++++++++++++++++++++++++++++++++分割线+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

前面是不是很草率

好,再来一道例题:

那道题就是八数码,输入开始序列,求出它到

1 2 3
8 0 4
7 6 5

最少移动几步,如果不能在\(5000\)步以内求解,输出\(-1\)

这道题BFS大家都会吧,把状态还能状压,我们讲的是把状压后状态康托进一步节省空间。

Code(RE30%):

//#define DEBUG
#ifdef DEBUG
#include<windows.h>
#include<conio.h>
#include<ctime>
#include<cstdio>
#define JG puts("----------------------------------");
#define wait(time) Sleep(time*1000);
#define Get getch();
#define cls system("cls");
#endif // DEBUG
#include<iostream>
#include<cstring>
#define max(x,y) {(x)>(y)?(x):(y)}      //优化
#define min(x,y) {(x)<(y)?(x):(y)}
//#define DEFINE_LIQUEUE
//#define USE_QuickIO
template<typename T>                    //交换
inline void Swap(T& x,T& y){T tmp=x;x=y;y=tmp;}
using namespace std;
int a[9];                                //定义目标布局数组
typedef unsigned long long ull;
#ifdef DEFINE_LIQUEUE
namespace LiQueue                        //使用namespace防止CE
{
    template<typename T>
    class LiQueue                        //定义queue
    {
        typedef T *TPoint;
        T Rear,Front;
        //使用单链表可以避免数组开太大
        //并且queue中只需要插入尾部和删除头部,很适合链表。
        class slist
        {
            struct node                  //节点结构体
            {
                T data;
                node* next;
            }*head;
            T rear;                      //为了方便使用队列加了个rear
        public:
            slist(){head=NULL;}          //默认头指针指向NULL
            bool empty(){return !head;}  //链表是否为空
            void Insert_head(T data)     //在头部插入
            {
                node* NewNode=new node;  //申请新节点
                NewNode->data=data;      //设定data
                if (this->empty())       //如果链表为空
                    NewNode->next=NULL;  //则next为NULL
                else NewNode->next=head->next->next; //否则指向下一个节点
                head->next=NewNode;      //将头节点指向它
                rear=data;               //标记rear
            }
            void Delete_tail()
            {
                if (empty()) return ;
                if (head->next->next==NULL)
                {
                    head->next=head=NULL;
                    return ;
                }
                node* For=head;          //遍历用的节点
                while (For->next->next)  //遍历到目标结点向前一个节点。
                    For=For->next;       //下一个节点
                For->next=NULL;          //直接置NULL,就不垃圾回收了。
                rear=For->data;          //标记rear
            }
            T GetRear(){return rear;}
        }Queue;
    public:
        LiQueue(){}
        bool empty(){return Queue.empty();}           //队列是否为空
        void push(T data){Queue.Insert_head(data);}   //入队时插入头部
        T front(){return Queue.GetRear();}            //取尾部
        void pop(){Queue.Delete_tail();}              //出队时删除尾部
    };
}
#endif
#ifdef USE_QuickIO
typedef unsigned long long ull;
struct READ
{
    template<typename type>
    inline READ& operator>> (type& num)
    {
        register char c=getchar(),w=1;
        while('0'>c||c>'9'){if(c==EOF) return *this;w=c=='-'?-1:1;c=getchar();}
        num=0;
        while ('0'<=c&&c<='9'){num=(num<<1)+(num<<3)+(c-'0');c=getchar();}
        num*=w;
        return *this;
    }
}cin;
class WRITE
{
    private:
        char out[1<<10],*top;
    public:
        inline WRITE(){top=out;}
        inline ~WRITE(){fwrite(out,1,top-out,stdout);}
        inline WRITE& operator<< (char c){
            *(top++)=c;
            if (top==out+(1<<20)) fwrite(top=out,1,1<<20,stdout);
            return *this;
        }
        inline WRITE& operator <<(ull num){
            if(num==0) return *this;
            return *this<<num/10<<(char)(num%10+'0');
        }
        template<typename type>
        inline WRITE& operator <<(type & num){
            if(num==0) return *this<<'0';
            if(num>0) return *this<<(ull)(num);
            return *this<<'-'<<(ull)(-num);
        }
}cout;
#endif
const ull fact[20]={1,1,2,6,24,120,720,5040,40320,362880,3628800,39916800,479001600,6227020800,87178291200,1307674368000}; //1~15阶乘表
const int UpperBound=5000;                        //上界
const int EndCantor=46686,EndValue=123804765;     //初始布局的康托展开值和初始布局状压后的值。
int StartCantor,StartValue;                       //结束布局的康托展开值和初始布局状压后的值。
const int N=370000;                               //状态最大9!=362880
const short dx[4]={0,0,-1,1},dy[4]={1,-1,0,0};    //方向数组
bool vis[N];                                      //标记数组
int step[N];
inline void Input(){cin>>a[0]>>a[1]>>a[2]>>a[3]>>a[4]>>a[5]>>a[6]>>a[7]>>a[8];}//输入
ull Cantor(int n,int a[15])                       //对于n的一个排列a进行康托展开
{
    ull ans=0;                                    //因答案可能很大所以用ull
    for (int i=0;i<n;i++)
    {
        int x=0;                                  //x代指公式中a[i],节省空间
        for(int j=i+1;j<n;j++)                    //计算公式中a[i]
            if (a[j]<a[i]) ++x;
        ans+=x*fact[n-i-1];
    }
    return ans+1;                                 //答案要+1
}
void CantorReverse(long long r,int len,int a[])    //康托展开逆运算,结果在a中
{
    r--;                                           //初始r要减1
    bool vis[20]={0};                              //vis[i]用来标记是否排列中有数字i
    for(int i=1;i<=len;i++)
    {
        long long tp=r/fact[len-i];                //得出商,确定初始值
        r-=tp*fact[len-i];                         //用减法代替取模加快运算
        int j;
        for(j=0;j<len;j++)                         //求出i位上数字
            if(!vis[j]){if(!tp) break;--tp;}       //依次检查vis并标记tp
        vis[j]=1;
        a[i-1]=j;
    }
}
void bfs()
{
    memset(step,-1,sizeof step);                  //step数组初始化为-1
    int Q[N],rear,front;                          //Q队列用来存储已探索状态状压后康托展开的值。
    rear=front=0;                                 //初始化
    vis[StartCantor]=true;                        //已访问本身
    step[StartCantor]=0;                          //到本身的步数为0
    Q[rear]=StartCantor,++front;                  //放入初始状态
    while (rear!=front)
    {
        int t=Q[rear];                            //取出队头状态
        ++rear;
        if (t==EndCantor) {cout<<step[t];return ;} //如果是结束状态
        vis[t]=true;
        int cp[9],p[3][3];                        //cp:还原后一维数组,p:还原后二维数组
        CantorReverse(t,9,cp);                    //还原
        p[0][0]=cp[0];p[0][1]=cp[1];p[0][2]=cp[2];//一维数组转二维
        p[1][0]=cp[3];p[1][1]=cp[4];p[1][2]=cp[5];
        p[2][0]=cp[6];p[2][1]=cp[7];p[2][2]=cp[8];
#ifdef DEBUG
        JG;
        cout<<"现在的状态:\n";
        for (int i=0;i<3;i++)
        {
            for (int j=0;j<3;j++)
                cout<<p[i][j]<<' ';
            cout<<'\n';
        }
        JG;
        cout<<"现在的CP:";
        for (int i=0;i<9;i++) cout<<cp[i]<<' ';
        cout<<'\n';
#endif // DEBUG
        int x,y;
        for (int i=0;i<3;i++)                     //找到0的坐标
            for (int j=0;j<3;j++)
                if (!p[i][j]) {x=i,y=j;break;}
#ifdef DEBUG
        cout<<"找到的0的坐标:("<<x<<','<<y<<")\n";
        Get;
        cls;
#endif // DEBUG
        for (int i=0;i<4;i++)                     //扩展新状态
        {
            int tx=x+dx[i],ty=y+dy[i];            //新坐标
            if (tx>=0&&tx<3&&ty>=0&&ty<3)         //不越界
            {
#ifdef DEBUG
                JG;
                cout<<x<<"->"<<tx<<'\n';
                cout<<y<<"->"<<ty<<'\n';
#endif // DEBUG
                Swap(p[tx][ty],p[x][y]);          //新状态
                for (int ii=0;ii<9;ii++)          //转一维
                    cp[ii]=p[ii/3][ii%3];
#ifdef DEBUG
                    cout<<"更新状态:\n";
                    for (int i=0;i<3;i++)
                    {
                        for (int j=0;j<3;j++)
                            cout<<p[i][j]<<' ';
                        cout<<'\n';
                    }
                    cout<<"更新的CP:";
                    for (int i=0;i<9;i++) cout<<cp[i]<<' ';
                    cout<<'\n';
#endif // DEBUG
                int NowCantor=Cantor(9,cp);       //Cantor处理
                if (vis[NowCantor])               //重复
                {
                    Swap(p[tx][ty],p[x][y]);      //恢复原状态
                    continue;
                }
                Q[front]=NowCantor;               //入队
                ++front;
                step[NowCantor]=step[t]+1;
                if (step[NowCantor]>UpperBound){cout<<-1;return ;}
                Swap(p[tx][ty],p[x][y]);          //恢复原状态
            }
        }
#ifdef DEBUG
        cls;
#endif // DEBUG
    }
}
int main()
{
#ifdef FILE
    freopen("Eight-figure Puzzles.in","r",stdin);
    freopen("Eight-figure Puzzles.in","r",stdout);
#endif // OPEN FILE
    Input();                           //输入
#ifdef DEBUG
    cls;                               //清屏
#endif // DEBUG
    int z=0;
    for (int i=0;i<9;i++) z=z*10+a[i]; //获取数字
    StartCantor=Cantor(9,a);             //设置结束Cantor与Value
    StartValue=z;
    bfs();                             //Breadth First Search
#ifdef FILE
    fclose(stdin);
    fclose(stdout);
#endif // CLOSE FILE
    return 0;
}

原文地址:https://www.cnblogs.com/CDOI-24374/p/12274692.html

时间: 2024-10-29 04:29:39

浅谈康托展开和其逆运算的相关文章

关于数论【康托展开及其逆运算】

表示这个东西背了很多次,但是次次忘,希望这次能够记住吧. 康托展开:问45231是n=5的全排列中第几个排列?ans:= 3*4! + 3*3! + 1*2! + 1*1! + 0*0! =93这时求出的是在45231前面全部的排列,排名还要加1所以对此的做法,就是将阶乘前面的求出来,这个就是在a[i]前面,还没出现过的数字.比如4前面1~3都没出现而1(或2或3)xxxx肯定在4xxxx前面,因为有四个不定的数字,所以乘上4! 逆运算:问n=5的全排列中第94个是谁?94先-194/4!=3.

康托展开及其逆运算 详解

前文: 这个东东是我准备进攻一道A*算法的八数码题目时,遇到的. 决定先搞懂这个,再进攻八数码(传说中  不做人生不完整的 题目). 康托展开是什么? 定义: X=an*(n-1)!+an-1*(n-2)!+...+ai*(i-1)!+...+a2*1!+a1*0! ai为整数,并且0<=ai<i(1<=i<=n) 简单点说就是,判断这个数在其各个数字全排列中从小到大排第几位. 比如 132,在1.2.3的全排列中排第2位. 康托展开有啥用呢? 维基:n位(0~n-1)全排列后,其

.net中对象序列化技术浅谈

.net中对象序列化技术浅谈 2009-03-11 阅读2756评论2 序列化是将对象状态转换为可保持或传输的格式的过程.与序列化相对的是反序列化,它将流转换为对象.这两个过程结合起来,可以轻松地存储和传输数 据.例如,可以序列化一个对象,然后使用 HTTP 通过 Internet 在客户端和服务器之间传输该对象.反之,反序列化根据流重新构造对象.此外还可以将对象序列化后保存到本地,再次运行的时候可以从本地文件 中“恢复”对象到序列化之前的状态.在.net中有提供了几种序列化的方式:二进制序列化

浅谈二维中的树状数组与线段树

一般来说,树状数组可以实现的东西线段树均可胜任,实际应用中也是如此.但是在二维中,线段树的操作变得太过复杂,更新子矩阵时第一维的lazy标记更是麻烦到不行. 但是树状数组在某些询问中又无法胜任,如最值等不符合区间减法的询问.此时就需要根据线段树与树状数组的优缺点来选择了. 做一下基本操作的对比,如下图. 因为线段树为自上向下更新,从而可以使用lazy标记使得矩阵的更新变的高校起来,几个不足就是代码长,代码长和代码长. 对于将将矩阵内元素变为某个值,因为树状数组自下向上更新,且要满足区间加法等限制

浅谈Android多屏幕的事

浅谈Android多屏幕的事 一部手机可以同时看片.聊天,还可以腾出一支手来撸!这么吊的功能(非N版本,非第三方也能实现,你不知道吧)摆在你面前,你不享用?不关注它是怎样实现的?你来,我就满足你的欲望! 一部手机可以同时看片.聊天,还可以腾出一支手来撸==!就像这样: 是时候告别来回切换应用屏幕的酸爽了,还可以在分屏模式下两Activity间直接拖放数据! 好高大上的样子!这是怎么实现的?别急,我们一一道来: kitkat(4.4)版本对多任务分屏的实现 由于相关的代码和功能被封装及隐藏起来,所

浅谈软件工程

借鉴<构造之法>--浅谈软件工程 源程序就是代码,建立在数据结构之上,对数据进行操作.数据分为静态数据和动态数据. 软件构建不仅仅是cc和link命令,一个复杂的软件具有合理的软件架构.软件设计,实现等等.软件团队要从需求分析开始,把合适需求梳理出来,然后展开后续工作,如软件架构设计,写数据结构和算法,测试到最后发布软件. 由“软件=程序+软件工程”扩展出“软件企业=软件+商业模式” 程序是基本功,软件工程决定了软件的质量,商业模式决定了一个软件企业的成败,软件从业人员的道德操守会极大的影响软

浅谈HTTP响应拆分攻击

在本文中,我们将探讨何谓HTTP响应拆分以及攻击行为是怎样进行的.一旦彻底理解了其发生原理(该原理往往被人所误解),我们就可以探究如何利用响应拆分执行跨站点脚本(简称XSS).接下来自然就是讨论如果目标网站存在响应拆分漏洞,我们要如何利用这一机会组织CSRF(即跨站点伪造请求)攻击.最后,我们一起来看看哪些预防措施能够抵御这些攻击行为.如果大家对这个话题感兴趣,不妨继续读下去. 什么是HTTP响应拆分? 首先让我们设想一下某个具备多种语言选项的页面.该页面的默认语言为英语,但其中同时具备一个下拉

浅谈网站优化六步骤

1.分析关键词 这是进行SEO最重要的一环,关键词分析包括:关键词关注量分析.竞争对手分析.关键词与网站相关性分析.关键词部署.关键词排名预测. 2.网站架构分析 网站结构符合搜索引擎的蜘蛛喜好则有利于SEO.其中网站架构分析包括:剔除网站架构不友好设计.尽量使用树状目录结构.网站导航与链接优化. 2.关键词布局 SEO不仅仅只让网站首页在搜索引擎有好的排名,更为重要的是让网站的每个页面都带来一定流量.所以我们要为每个页面单独建设独一无二的页面主题(title,description)以及页面正

nyist 139 我排第几个&amp;&amp;143 第几是谁(康托展开和逆康托展开)

 我排第几个 时间限制:1000 ms  |  内存限制:65535 KB 难度:3 描述 现在有"abcdefghijkl"12个字符,将其所有的排列中按字典序排列,给出任意一种排列,说出这个排列在所有的排列中是第几小的? 输入 第一行有一个整数n(0<n<=10000); 随后有n行,每行是一个排列: 输出 输出一个整数m,占一行,m表示排列是第几位: 样例输入 3 abcdefghijkl hgebkflacdji gfkedhjblcia 样例输出 1 3027