【bzoj2284】【SDOI2011】贪吃蛇【搜索】【位运算】【卡常大法好】
2015-03-09 16:29
302 查看
这道题真是太精妙了……
传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=2284
首先这题棋盘的范围是15不是12。
本来原题是有special Judge的,因为要输出方案……
但是这是oj嘛。。就输出最短时间好了- -
我一开始愚蠢的想法是xy各用一个char存,蛇长最多为8,就开个结构体数组,再开一个长度为4的记录事物的位置……然后还有什么当前时间,剩余等待时间。。。
因为bfs相当于在边权为1的图上做最短路,所以如果直接转移到旁边的格子会导致不一定最优解。
所以我们需要设置一个等待时间,当它不为零时就留在原地不动。。
然后写代码的时候犯了许许多多SB错误……
比如,判断蛇是否自交要在走完这步之后,然而我写成了这样:
……
然后哈希判重什么弄得很混乱,我把蛇形态一样位置一样的都视为相同的状态了……
反正各种混乱……
我觉得学习标程是必要的- -
于是我就看了题解。
真是太精妙了啊!!!!
std把队列节点的信息浓缩到了一个int里。。。
他是基于这样一种神奇却又真实的事实:只要蛇头、蛇的形态确定了,这条蛇在棋盘上就确定了。
所以可以预处理出蛇的所有合法形态,再加上蛇头坐标就能表示一条蛇了。
预处理也是bfs- -
怎么预处理呢?
首先确定蛇最多能有多少种形态。
把蛇头到蛇尾划为1、2、3、4…
先不管合不合法,2相对1的位置有4种(上下左右),3相对2的位置最多3种,4相对3的位置最多3种……
把蛇的各种长度下的各种形态加起来,最大不超过4×(32+33+34+35)=14404 \times(3^2 + 3^3 + 3^4 + 3^5 ) = 1440
蛇身的每一个部分相对于前一个部分的位置可以用2个bit表示。蛇头不必表示(因为蛇头是最前面的),可以用这两个bit存这条蛇的长度(长度就四种情况4、5、6、7, 8的时候已经到了目标状态,可以出解了,所以不必存),获取长度的时候加上4就行了。
预处理完后就可以把具体状态丢掉了,只存这个状态在队列中的下标(<211\lt 2^{11})(精妙啊!)
等待时间有7种(因为1≤能走的Aij≤81\le 能走的A_{ij} \le 8)可以用3个bit存。
然后食物的状态(吃了还是没吃)要4个bit。
棋盘上的坐标可以用两个4bit一共8个二进制位存。
8+4+3+11=26.
代码里visit[ q[1] >> 3 ]里存的是蛇在这样的位置上有没有这样的等待时间,有的话则visit[q[1]>>3]里的第waitTime-1个二进制位就是1。
然后注意一下
waitTime1就不用减一了……
下面贴上我这丑陋的代码……
(PS:队列不用初始化,这样能快很多……)
搜索真是美丽啊!
传送门: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)=14404 \times(3^2 + 3^3 + 3^4 + 3^5 ) = 1440
蛇身的每一个部分相对于前一个部分的位置可以用2个bit表示。蛇头不必表示(因为蛇头是最前面的),可以用这两个bit存这条蛇的长度(长度就四种情况4、5、6、7, 8的时候已经到了目标状态,可以出解了,所以不必存),获取长度的时候加上4就行了。
预处理完后就可以把具体状态丢掉了,只存这个状态在队列中的下标(<211\lt 2^{11})(精妙啊!)
等待时间有7种(因为1≤能走的Aij≤81\le 能走的A_{ij} \le 8)可以用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; }
搜索真是美丽啊!
相关文章推荐
- BZOJ 2284 贪食蛇(Sdoi2011 虐心搜索)
- BZOJ.2246.[SDOI2011]迷宫探险(DP 记忆化搜索 概率)
- BZOJ 2245: [SDOI2011]工作安排( 费用流 )
- [树链剖分] [bzoj2243] [SDOI2011]染色
- 【题解】Casting Spells LA 4975 UVa 1470 双倍回文 SDOI 2011 BZOJ 2342 Manacher
- BZOJ2243: [SDOI2011]染色
- Bzoj 2243: [SDOI2011]染色(树链剖分+线段树)
- BZOJ2242 SDOI2011 计算器calc
- BZOJ 2242 [SDOI2011]计算器 ——EXGCD/快速幂/BSGS
- 【bzoj2242】[SDOI2011]计算器 数论相关(快速幂+扩展欧几里得+BSGS)
- 【BZOJ2243】【SDOI2011】染色
- BZOJ2243 [SDOI2011]染色 题解&代码
- [bzoj3990][SDOI2015]排序-搜索
- BZOJ2286 [Sdoi2011消耗战
- BZOJ 2243: [SDOI2011]染色 树链剖分+线段树区间合并
- 【BZOJ2243】[SDOI2011]染色 树链剖分
- 【BZOJ】【2242】【SDOI2011】计算器
- BZOJ 2286: [Sdoi2011]消耗战
- bzoj2242 [SDOI2011]计算器
- [BZOJ2244][SDOI2011]拦截导弹 CDQ分治