hihocoder1233 搜索+状态压缩 我是菜比

题意:有n个卡槽,放有体积不同的n个空盒子,每次你可以移动一个空盒子到相邻卡槽,但前提是相邻卡槽若已经有空盒子,那么要移动的空盒子体积必须小于已有的空盒子,问要移动多少步才能使得从左到右,每个卡槽空盒子的体积递增。

思路:就是搜索,但是需要想出一种办法来表示当前搜索的状态。一开始我自己想的时候是想每个位置给他来个n位的二进制,来记录这个位置上面有哪几个盒子,但是想了想这样记录是不太好做的,每种状态就要用n个数字去记录,还要从每个位置上找到体积最小的那个盒子,很不好弄,就放弃了。比赛过程中学长给我草草说了一边一个方法,但是没听懂。今天补题的时候又问了问才弄明白是怎么一回事。因为n的大小不确定,但是最后得到的盒子体积都是升序排列,所以就可以从最后的序列搜到每种排列状态,这样做到预处理一遍,记录下来,然后查询O(1)。

那么问题是如何记录当前的状态呢?因为就7个位置么,所以可以用3位二进制数来表示第i个盒子的在哪个位置,这些盒子的位置可以汇总到一起,用最多21位二进制数表示一个状态,这样状态就相较于我之前的想法好表示的很多,也易操作了许多。我们用pos[i]表示升序排列的第i个盒子当下处在哪个位置,比如说12345是升序排列,43251是一种情况,那么升序排列的第1个盒子位于43251的第5个位置,那么我们将pos[i]压缩成一个数

   for(int i=0;i<n;i++) {code=code|(pos[i]<<(i*3));}

表示当下状态中升序排列的每一个盒子所处的位置。然后我们还需要一个fis数组,来记录当前状态下每个位置的最小体积的盒子的编号,我们需要用fis数组来进行盒子的挪动,同时改变挪动盒子的pos,然后进行bfs。上面提到的第i个盒子所处的位置,每个位置的最小体积合资的编号呀都是升序排列中的盒子编号,因为升序排列中的盒子编号与其体积大小对应,所以省去了记录盒子体积的操作。

原本不想贴代码了,因为不是我写的,但是怕以后自己光看原理看不懂就把代码一起贴上了。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <set>
#include <queue>
#include <algorithm>
#include <cmath>

#define cl(a,b) memset(a,b,sizeof(a))
#define maxn 0x77777777

using namespace std;

int da[8];
int res[2200100];
int pos[8];
int fis[8];
typedef struct data
{
    int pos,rs;
}D;
D dat[9];
int cmpp(D a,D b)
{
    return a.pos<b.pos;
}
int cmpr(D a,D b)
{
    return a.rs<b.rs;
}
void decode(int code,int cn)
{
    int i,j;
    for(i=0;i<cn;i++)
    {
        pos[i]=code&7;///
        code=code>>3;
    }
    cl(fis,0x77);
    for(i=0;i<cn;i++)
    {
        fis[pos[i]]=min(fis[pos[i]],i+1);///
    }
}
int tocode(int cn)///yasuo
{
    int code=0;
   for(int i=0;i<cn;i++)
   {
       code=code|(pos[i]<<(i*3));
   }
   return code;
}
void bfs(int cn)
{
    queue<int> que;
    int code=0,i,j,k,np,po,f;
    for(i=0;i<cn;i++)
    {
        code=code|((i+1)<<(3*i));///
    }
    que.push(code);
    res[code]=0;
    while(!que.empty())
    {
        f=que.front();
        que.pop();
        decode(f,cn);
        for(i=0;i<cn;i++)
        {
            po=pos[i];
            np=i+1;
            if(fis[po]!=np)continue;
            if(po!=1)
            {
                if(fis[po-1]>np)
                {
                    pos[i]-=1;
                    code=tocode(cn);
                    if(res[code]==-1){
                        que.push(code);
                        res[code]=res[f]+1;
                    }
                    pos[i]+=1;
                }
            }
            if(po!=cn)
            {
                if(fis[po+1]>np)
                {
                    pos[i]+=1;
                    code=tocode(cn);
                    if(res[code]==-1){
                        que.push(code);
                        res[code]=res[f]+1;
                    }
                    pos[i]-=1;
                }
            }
        }
    }
}
int main()
{
    cl(res,-1);
    for(int i=1;i<8;i++){
        bfs(i);
    }
    int tt,ii,cn,i,j,k,code;
    cin>>tt;
    while(tt--)
    {
        scanf("%d",&cn);
        for(i=0;i<cn;i++)
        {
            scanf("%d",&dat[i].rs);
            dat[i].pos=i+1;
        }
        sort(dat,dat+cn,cmpr);///
        for(i=0;i<cn;i++)
        {
            pos[i]=dat[i].pos;///
        }
        code=tocode(cn);
        printf("%d\n",res[code]);
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值