【bzoj2284】【SDOI2011】贪吃蛇【搜索】【位运算】【卡常大法好】

7 篇文章 0 订阅
3 篇文章 0 订阅

这道题真是太精妙了……
传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=2284
首先这题棋盘的范围是15不是12。
本来原题是有special Judge的,因为要输出方案……
但是这是oj嘛。。就输出最短时间好了- -
我一开始愚蠢的想法是xy各用一个char存,蛇长最多为8,就开个结构体数组,再开一个长度为4的记录事物的位置……然后还有什么当前时间,剩余等待时间。。。
因为bfs相当于在边权为1的图上做最短路,所以如果直接转移到旁边的格子会导致不一定最优解。
所以我们需要设置一个等待时间,当它不为零时就留在原地不动。。
然后写代码的时候犯了许许多多SB错误……
比如,判断蛇是否自交要在走完这步之后,然而我写成了这样:

            if(can(pos.i,pos.j))
                // !!!!!!!!!!willnoteatitself不能放在这里!
                {/*扩展状态。。。*/

……
然后哈希判重什么弄得很混乱,我把蛇形态一样位置一样的都视为相同的状态了……
反正各种混乱……
我觉得学习标程是必要的- -
于是我就看了题解。
真是太精妙了啊!!!!
std把队列节点的信息浓缩到了一个int里。。。
他是基于这样一种神奇却又真实的事实:只要蛇头、蛇的形态确定了,这条蛇在棋盘上就确定了。
所以可以预处理出蛇的所有合法形态,再加上蛇头坐标就能表示一条蛇了。
预处理也是bfs- -
怎么预处理呢?
首先确定蛇最多能有多少种形态。
把蛇头到蛇尾划为1、2、3、4…
先不管合不合法,2相对1的位置有4种(上下左右),3相对2的位置最多3种,4相对3的位置最多3种……
把蛇的各种长度下的各种形态加起来,最大不超过 4×(32+33+34+35)=1440
蛇身的每一个部分相对于前一个部分的位置可以用2个bit表示。蛇头不必表示(因为蛇头是最前面的),可以用这两个bit存这条蛇的长度(长度就四种情况4、5、6、7, 8的时候已经到了目标状态,可以出解了,所以不必存),获取长度的时候加上4就行了。
预处理完后就可以把具体状态丢掉了,只存这个状态在队列中的下标( <211 )(精妙啊!)
等待时间有7种(因为 1Aij8 )可以用3个bit存。
然后食物的状态(吃了还是没吃)要4个bit
棋盘上的坐标可以用两个4bit一共8个二进制位存。
8+4+3+11=26.
代码里visit[ q[1] >> 3 ]里存的是蛇在这样的位置上有没有这样的等待时间,有的话则visit[q[1]>>3]里的第waitTime-1个二进制位就是1。
然后注意一下

    int s1=mkStat(x1,y1,snakeStat1,foodStat1,waitTime1);
    if(!(visit[s1>>3] & 1 << waitTime1)){
        visit[s1>>3]|=1<< waitTime1;
        q[++tail]=s1;
        pred[tail]=head;
    }

waitTime1就不用减一了……
下面贴上我这丑陋的代码……
(PS:队列不用初始化,这样能快很多……)

#include<cstdio>
#include<cstring>
#include<iostream>
#include<cstdlib>
using namespace std;
const int maxr=17,maxc=17,maxq=15*15*1440*16*8,maxS=1<<11;
const int  dx[]={0,0,-1,1};
const int  dy[]={-1,1,0,0};
const char Dir[]={'L','R','U','D'};
int snakePos0[4][2];
int map[maxr][maxc],foodMap[maxr][maxc];
int stat[maxS][4],foodStat[maxS][4];
int q[maxq],pred[maxq];
unsigned char visit[1<<23];
char path[1000];
int ansTime,ansLink,foodCnt,pathLength,r,c;
int expand(int stat,int len,int k){
    int tmp=stat;
    len+=4;
    k^=1;
    int x=dx[k],y=dy[k];
    for(int i=2;i<len;++i){
        x+=dx[tmp&3];
        y+=dy[tmp&3];
        tmp>>=2;
        if(!x && !y) return -1;
    }
    return (stat<<2 & ((1<<(len+len-2))-1)) | k;
}
void calcStat(){
    //calculate the state of snake when it moves.
    memset(stat,0xff,sizeof stat);
    memset(foodStat,0xff,sizeof foodStat);
    int head=0,tail=1;
    int visit[1<<14]={0};
    for(int i=3;i>0;--i){
        int k=0;
        while(snakePos0[i-1][0]+dx[k]!=snakePos0[i][0]||snakePos0[i-1][1]+dy[k]!=snakePos0[i][1])
            ++k;
        q[1]=q[1]<<2|k;
    }
    q[1]<<=2;//头部没有“方向“,这两位是存储长度的。
    visit[q[1]]=1;
    while(head<tail){
        int s0=q[++head];
        int len0=s0&3;
        int snakeStat0=s0>>2;
        for(int k=0;k<4;++k){
            int snakeStat1=expand(snakeStat0,len0,k);
            if(snakeStat1>=0){
                int s1=(snakeStat1 ) << 2 |len0;
                if(!visit[s1]){
                    q[++tail]=s1;
                    visit[s1]=tail;
                }
                stat[head][k]=visit[s1];
            }
        }
        //grow
        for(int k=0;k<4;++k){
            int snakeStat1=expand(snakeStat0,len0+1,k);
            if(snakeStat1>=0){
                if(len0<3){
                    int s1=(snakeStat1 ) << 2 |(len0+1);
                    if(!visit[s1]){//好像0xfff不加也行。。。
                        q[++tail]=s1;
                        visit[s1]=tail;
                    }
                    foodStat[head][k]=visit[s1];
                }
                else{
                    foodStat[head][k]=0;
                    //到达长度8,即到达目标状态
                }
            }
        }
    }
//  printf("%d %d",head,tail);//tail=1200!!!
}
#define mkStat(x,y,ss,fs,wt) (((x)<<22)|((y)<<18)|((ss)<<7)|((fs)<<3)|(wt))
void bfs(){//这里的 ss是上面calcStat中队列的下标
//  memset(q,0,sizeof q);
//  memset(visit,0,sizeof visit);
    int head=0,tail=1;
    q[1]=mkStat(snakePos0[0][0],snakePos0[0][1],1,(1<<foodCnt)-1,0);
    visit[q[1]>>3]|=1<<0;
    while(head<tail){
        int s0=q[++head];
        int x0          = s0 >> 22 & 0xf,
            y0          = s0 >> 18 & 0xf,
            snakeStat0  = s0 >>  7 & 0x7ff,
            foodStat0   = s0 >>  3 & 0xf,
            waitTime0   = s0 & 7;
            if(!foodStat0 && !waitTime0){
                ansLink=head;
    //          printf("Anslink=%d\n",ansLink);
                break;
            }
            if(waitTime0){
                int s1=mkStat(x0,y0,snakeStat0,foodStat0,waitTime0-1);
                if(!(visit[s1>>3] & 1<<(waitTime0-1))){
                    visit[s1>>3]|=1<<(waitTime0-1);
                    q[++tail]=s1;
                    pred[tail]=head;
                }
            }
            else{
                for(int k=0;k<4;++k){
                    int x1=x0+dx[k],
                        y1=y0+dy[k],
                        snakeStat1=stat[snakeStat0][k],
                        foodStat1=foodStat0,
                        waitTime1=abs(map[x1][y1]-map[x0][y0]);
                        if(!map[x1][y1]) continue;
                        if(foodMap[x1][y1]&&(foodStat1& 1<< (foodMap[x1][y1]-1))){
                            foodStat1^=1<<(foodMap[x1][y1]-1);
                            snakeStat1=foodStat[snakeStat0][k];
                        }
                        else snakeStat1=stat[snakeStat0][k];
                        if(snakeStat1>=0){                      
                            int s1=mkStat(x1,y1,snakeStat1,foodStat1,waitTime1);

                            if(!(visit[s1>>3] & 1<< waitTime1)){//不要减一!!不要减一!!
                                visit[s1>>3]|=1<< waitTime1;
                                q[++tail]=s1;
                                pred[tail]=head;
                            }
                        }
                }
            }
    }
//  printf("%d\n",tail);
}
inline void input(){
    scanf("%d%d",&r,&c);
    char str[20];
    for(int i=1;i<=r;++i){
        scanf("%s",str);
        for(int j=0;j<c;++j){
            map[i][j+1]=str[j]-48;
        }
    }
    for(int i=0;i<4;++i)
        scanf("%d%d",&snakePos0[i][0],&snakePos0[i][1]);
    int x,y;
    scanf("%d",&foodCnt);
    for(int i=1;i<=foodCnt;++i){
        scanf("%d%d",&x,&y);
        foodMap[x][y]=i;
    }
}
inline void calcAns(){
    bfs();
    int p1=ansLink;
    int x0,y0,x1,y1,wt;
    x1=q[p1]>>22&0xf;
    y1=q[p1]>>18&0xf;
    while(p1>1){
        int p0=pred[p1];
        x0=q[p0]>>22 & 0xf;
        y0=q[p0]>>18 & 0xf;
//      wt=q[p0]&7;
        ansTime++;
/*      if(!wt){
            int k=0;
            while(x0+dx[k]!=x1||y0+dy[k]!=y1)
                ++k;
            path[pathLength++]=Dir[k];
        }
*/      x1=x0,y1=y0;
        p1=p0;
    }
//  reverse(path,path+pathLength);
}
inline void output(){
    if(!ansLink)
        puts("No solution.");
    else{
        printf("%d\n",ansTime);
//      for(int i=0;i<pathLength;++i)
//          putchar(path[i]);
//      putchar('\n');
    }
}
int main(){
    input();
    calcStat();
    calcAns();
    output();
    return 0;
}

搜索真是美丽啊!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值