康拓展開
已知有一集合A包含n個不同的元素,其中(k1,k2,k3...,kn-2 )是A的一個排列。假設此排列爲A按字典序從小到大排列的排列中的第x個排列,則x=a1(n-1)!+a2(n-2)!+...an-2*1!+an-1*0! (其中ai爲ki+1...kn中比ki小的數的個數)
例如:
3214是1234的第2*3! +1*2! +0*1! +0*0! =14 個排列。
實現代碼:
int cantos(){//康拓展開函數部分
memset(used,false,sizeof(used));used記錄一個數是否已用
int ans=0;
for(int i=1;i<=n;i++){
int cnt=0;
for(int j=1;j<a[i];j++){
if(!used[j]){//累加比a[i]小的數
++cnt;
}
}
ans+=cnt*fact(n-i);//fact函數計算階乘
used[a[i]]=true;
}
return ans;
}
康拓逆展開
已知有一集合A包含n個不同的元素,如果按字典序從小到大排序,A的排列S是第x個排列,
則:s1=x/(n-1)! (取整)
s2=x%(n-1)!/(n-2)!
......
sn=0 (si表示ki+1,ki+2...kn中比ki小的數的個數)
例如:
A包含4個元素,S是A的第14個排列。
s1=14/6=2
s2=14%6/2=1
s3=14%6%2/1=0
s4=0
實現代碼:
int main() {
memset(used,false,sizeof(used));
cin>>n>>x;
for(int i=1;i<=n;i++){
int ai=x/fact(n-i)+1;//fact函數計算階乘
x%=fact(n-i);
for(int j=1;j<=n;j++){
if(!used[j]){//used記錄這個數有沒有用過
--ai;
if(ai==0){
p[i]=j;
used[j]=true;
break;
}
}
}
}
for(int i=1;i<=n;i++){
cout<<p[i]<<" ";
}
cout<<endl;
return 0;
}
應用
康拓展開主要用來標記一個排列。
如果我們直接用整數來表示排列,那麼對於有9個數的排列,你要開一個a[987654321]這麼大的數組;
而如果我們用康拓展開,只需要開a[362800]這麼大。
ps:如果數據過大,有時直接不用康拓展開空間更小。
經典題型
8數碼問題:
8方塊移動遊戲要求有1~8個數字方塊和一個空方塊(用0表示),每一步可以將空方塊與相鄰方塊互換。現在給出起始狀態和目標狀態,要求用最少的步數從起始狀態轉換成目標狀態,無解輸出-1。
用康拓展開記錄狀態,分別編號,然後直接廣搜即可。
AC code:
#include<iostream>
#include<queue>
#include<cstring>
using namespace std;
int a[11],s,t,fact[11],d[4]={-3,3,-1,1};
bool b[362885],used[11];
struct data{
int x,step;
};
queue<data> q;
int can(){
memset(used,0,sizeof(used));
int ans=0;
for(int i=1;i<=9;i++){
int cnt=0;
for(int j=1;j<a[i];j++){
if(!used[j])cnt++;
}
ans+=cnt*fact[9-i];
used[a[i]]=1;
}
return ans;
}
int main(){
fact[0]=1;
for(int i=1;i<=9;i++){
cin>>a[i];
if(a[i]==0)a[i]=9;
fact[i]=fact[i-1]*i;
}
s=can();//起始狀態編號
b[s]=1;
q.push((data){s,0});
for(int i=1;i<=9;i++){
cin>>a[i];
if(a[i]==0)a[i]=9;
}
t=can();//目標狀態編號
bool flag=0;
while(!q.empty()){
memset(used,0,sizeof(used));
data u=q.front();
q.pop();
if(u.x==t){
cout<<u.step;
flag=1;
break;
}
int now;
int x=u.x;
for(int i=1;i<=9;i++){//轉換爲數組
int ai=x/fact[9-i]+1;
x%=fact[9-i];
for(int j=1;j<=9;j++){
if(!used[j]){
--ai;
if(ai==0){
a[i]=j;
if(j==9)now=i;
used[j]=1;
break;
}
}
}
}
for(int i=0;i<4;i++){
int nx=now+d[i];
if(i==2&&now%3==1)continue;
if(i==3&&now%3==0)continue;
if(nx>=1&&nx<=9){
a[now]^=a[nx];//位運算交換兩數
a[nx]^=a[now];
a[now]^=a[nx];
int bian=can();
if(b[bian]){
a[now]^=a[nx];
a[nx]^=a[now];
a[now]^=a[nx];
continue;
}
b[bian]=1;
q.push((data){bian,u.step+1});
a[now]^=a[nx];
a[nx]^=a[now];
a[now]^=a[nx];
}
}
}
if(!flag)cout<<"-1";
return 0;
}