Sicily 1151. 魔板

题目大意:

类似于华容道的按规则移动方块,达到预期状态。

算法思想及主要数据结构:

BFS(广搜)+ 康托展开 + Queue

1、BFS

I.   将初始状态放入队列,

II.  得到队列中的第一个状态,则从队列中pop

III. 得到pop出的状态与目标太进行比较,如果匹配则结束,否则对当前状态继续分别进行A、B、C三种操作,把得到的新状态依次放入队列。

IV.只要队列不为空,重复II操作。

 2、康托展开

康托展开是一种特殊的哈希函数,它的使用范围是对于n个数的排列进行状态的压缩和存储,例如要对9的全排列进行判重.没有必要开一个10^9的数组,同时内存也不允许开到那么大的数组.对此,有人提出了优化,即对于一个n的排列数,没有必要开到10^n,因为在一个排列中每个数只出现一次,所以只要前n-1位确定了,前N位就确定了。

但是以上的想法仍不是可行的,因为N可以很大,例如15,所以便引入了康托展开:只需要确定这个排列在总的排列情况中是第几小的就可以了。

例如: {1,2,3,4,...,n}表示1,2,3,...,n的排列如 {1,2,3} 按从小到大排列一共6个。123 132 213 231 312321 。

代表的数字 1 2 3 4 5 6 也就是把10进制数与一个排列对应起来。

他们间的对应关系可由康托展开来找到。

如我想知道321是{1,2,3}中第几个小的数可以这样考虑 :

第一位是3,当第一位的数小于3时,那排列数小于321 如 123、 213 ,小于3的数有1、2 。所以有2*2!个。再看小于第二位2的:小于2的数只有一个就是1 ,所以有1*1!=1 所以小于321的{1,2,3}排列数有2*2!+1*1!=5个。所以321是第6个小的数。 2*2!+1*1!+0*0!就是康托展开。

再举个例子:1324是{1,2,3,4}排列数中第几个大的数:第一位是1小于1的数没有,是0个 0*3! 第二位是3小于3的数有1和2,但1已经在第一位了,所以只有一个数2 1*2! 。第三位是2小于2的数是1,但1在第一位,所以有0个数 0*1! ,所以比1324小的排列有0*3!+1*2!+0*1!=2个,1324是第三个小数。

3、A、B、C操作的数学方法

A操作(结果存入m,n变量中)

after.x= curr.y;

after.y= curr.x;

B操作

after.x= after.x + curr.x[3]+curr.x[0]+curr.x[1]+curr.x[2]; 

after.y= after.y + curr.y[3]+curr.y[0]+curr.y[1]+curr.y[2]; 

C操作:

after.x = after.x +curr.x[0]+curr.y[1]+curr.x[1]+curr.x[3]; 

after.y = after.y +curr.y[0]+curr.y[2]+curr.x[2]+curr.y[3]; 

 

#include <iostream>
#include <cstring>
#include <queue> 

using namespace std;

struct Record{
    string x,y;
    string op;   
}start,target;

const int PermSize = 8;
int factory[] = { 1, 1, 2, 6, 24, 120,720, 5040 }; // 7的阶乘(n-1的阶乘) 
bool isVisited[50000];

void initial(){
    
    string tmp = ""; 
            
    // 初始化初态魔板 
    start.x = "1234";
    start.y = "8765";
    start.op = "";
    
    target.x = "";
    target.y = "";  
    
    for(int i = 0;i < 4;i++){  
       cin >> tmp; 
       target.x += tmp;  
    }  
    for(int j = 0;j < 4;j++){  
       cin >> tmp; 
       target.y += tmp;  
    }  
    
    memset(isVisited, false ,sizeof(isVisited)); 
}

int cantor( string buf ) 
{
    int i, j, counted;
    int result = 0;
    for ( i = 0; i < PermSize; ++i ) 
    {
        counted = 0;
        for( j = i + 1; j < PermSize + 1; ++j )
            if( buf[i] > buf[j] )
                ++counted;
        result = result + counted * factory[PermSize - i - 1];
    }
    return result;
}

bool isEqual(const Record &A,const Record &B){
    return ( A.x == B.x && A.y == B.y ); 
}

Record operate(Record curr, Record after,int operation)  
{  
    switch(operation){
        case 0: 
            after.x = curr.y; 
            after.y = curr.x;  
            after.op += "A";
            break;  
        case 1: 
            after.x = after.x + curr.x[3]+curr.x[0]+curr.x[1]+curr.x[2];  
            after.y = after.y + curr.y[3]+curr.y[0]+curr.y[1]+curr.y[2];  
            after.op += "B"; 
            break;
        case 2: 
            after.x = after.x + curr.x[0]+curr.y[1]+curr.x[1]+curr.x[3];  
            after.y = after.y + curr.y[0]+curr.y[2]+curr.x[2]+curr.y[3];  
            after.op += "C";
            break;  
    }
    return after;  
}  

// 广搜 
void bfs( int m ){
    if( isEqual(start,target) ){ 
        cout << 0 << endl;
        return;
    }
    queue<Record> records;
    records.push(start);
    
    // 队列不为空 
    while( !records.empty() ){
        Record curr = records.front();
        records.pop();
        
        // 如果操作次数大于要求次数则返回-1 
        if( curr.op.size() > m ) {
            cout << -1 << endl;  
            return;
        }
        
        // 当前态与目标太匹配则返回 
        if( isEqual(curr, target) ){
            cout << curr.op.size() << " " << curr.op << endl; 
            return;
        }
        
        // 循环进行A,B,C操作 
        for( int i = 0; i < 3; i++ ){
            Record after;
            after.x = ""; 
            after.y =""; 
            after.op = curr.op;
            
            after = operate(curr,after,i);     
                   
            int cn = cantor( after.x + after.y ); 
            if( !isVisited[ cn ]){
                isVisited[ cn ] = true;
                records.push(after);
            }    
        }                              
    }       
}
 
int main(){
    
    int m;
    
    while( cin >> m && m != -1 ){
        initial();
        bfs(m);
    }        
    
    return 0;
}




  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值