康拓展開&康拓逆展開

康拓展開

已知有一集合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;
}

看不懂位運算的點這裏~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章