康托展开,是一种在\(\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