• 404 页面不存在


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

    我们以一道例题引入:

    排列的序号

    题目描述:

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

    输入描述:

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

    输出描述:

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

    输入输出样例:

    输入

    3
    123

    > ##### 输出
    > `1`
    > #### 数据范围
    > $nle 15$
    
    根据排列组合、加法原理等等,得出一个式子:
    $$ans=sum_{i=1}^n(a_{n+1-i}(n-i)!)$$
    ($a_i$表示原数的第$i$位在当前未出现的元素中是排在第几个)
    
    此为康托展开,代码如下:
    ```cpp
    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;
        }
    }
    int Q[N];                                         //Q队列用来存储已探索状态状压后康托展开的值。
    void bfs()
    {
        memset(step,-1,sizeof step);                  //step数组初始化为-1
        int rear,front;                               //rear和front记录队列首尾
        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<<"现在的状态:
    ";
            for (int i=0;i<3;i++)
            {
                for (int j=0;j<3;j++)
                    cout<<p[i][j]<<' ';
                cout<<'
    ';
            }
            JG;
            cout<<"现在的CP:";
            for (int i=0;i<9;i++) cout<<cp[i]<<' ';
            cout<<'
    ';
    #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<<")
    ";
            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<<'
    ';
                    cout<<y<<"->"<<ty<<'
    ';
    #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<<"更新状态:
    ";
                        for (int i=0;i<3;i++)
                        {
                            for (int j=0;j<3;j++)
                                cout<<p[i][j]<<' ';
                            cout<<'
    ';
                        }
                        cout<<"更新的CP:";
                        for (int i=0;i<9;i++) cout<<cp[i]<<' ';
                        cout<<'
    ';
    #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;
                    vis[NowCantor]=true;              //标记
                    if (step[NowCantor]>UpperBound){cout<<-1;return ;}
                    Swap(p[tx][ty],p[x][y]);          //恢复原状态
                    vis[NowCantor]=false;
                }
            }
    #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;
    }
    
    
  • 相关阅读:
    最近发现一个网站
    2017-0206 委托封装的方法的参数类型
    迈向Angular 2
    趣学CCNA 路由与交换
    HCNA 2017年01月26日
    在linux中使用phpize安装php扩展模块
    接口和抽象类
    C:Program Files (x86)MSBuildMicrosoft.Cppv4.0V110Microsoft.CppCommon.targets(611,5): error MSB
    抽象类和抽象方法
    java数组与内存控制
  • 原文地址:https://www.cnblogs.com/CDOI-24374/p/12274692.html
Copyright © 2020-2023  润新知