一看就知道是神搜,但是弄了好长时间一直是TimeLimited,开始的时候有剪枝的意识,但是全是小修小补,包括搜索到符合条件的及时回溯等方式,都还是不行,查资料才知道还有奇偶剪枝这回事,感觉很强大
关于奇偶剪枝:
应用范围:
给定你起点S,和终点D,之间有障碍,判断是否能在 T 时刻恰好到达终点D(下面的例题就是经典)
原理解释:
是数据结构的搜索中,剪枝的一种特殊小技巧。
现假设起点为(sx,sy),终点为(ex,ey),给定t步恰好走到终点,
s | ||||
| | ||||
| | ||||
| | ||||
+ | — | — | — | e |
如图所示(“|”竖走,“—”横走,“+”转弯),易证abs(ex-sx)+abs(ey-sy)为此问题类中任意情况下,起点到终点的最短步数,记做step,此处step1=8;
s | — | — | — | |
— | — | + | ||
| | + | |||
| | ||||
+ | — | — | — | e |
如图,为一般情况下非最短路径的任意走法举例,step2=14;
step2-step1=6,偏移路径为6,偶数(易证);
故,若t-[abs(ex-sx)+abs(ey-sy)]结果为非偶数(奇数),则无法在t步恰好到达;
返回,false;
反之亦反。
典型应用:题目来源:http://acm.hdu.edu.cn/showproblem.php?pid=1010
本题思路:深度搜索,对上下左右四个方向进行深搜,深搜前注意剪枝就好了...
#include <cstdlib> #include <cstring> #include <cmath> #include <cstdio> #include <iostream> using namespace std; int sum=1, row,line,doorrow,doorline,ttime,mark=0;//mark为是否可行的标记 char ch[10][10]={0}; void find(int i,int j){ if(mark==0){ if(sum==ttime&&doorrow==i&&doorline==j){ mark=1; } else if(sum<ttime&&mark==0){ if(i>0&&(ch[i-1][j]=='.'||ch[i-1][j]=='D')){//向上深搜 // cout<<"上移"<<" i:"<<i-1<<' '<<"j:"<<j<<" sum:"<<sum+1<<" time "<<time<<endl; sum++; ch[i-1][j]='X'; find(i-1,j); ch[i-1][j]='.';//还原 sum--; //还原 } if(mark==1) return ; if(i<row-1&&(ch[i+1][j]=='.'||ch[i+1][j]=='D')){//向下深搜 ch[i+1][j]='X'; sum++; find(i+1,j); ch[i+1][j]='.'; sum--; } if(mark==1) return ; if(j>0&&(ch[i][j-1]=='.'||ch[i][j-1]=='D')){////向左深搜 ch[i][j-1]='X'; sum++; find(i,j-1); ch[i][j-1]='.'; sum--; } if(mark==1) return ; if(j<line-1>0&&(ch[i][j+1]=='.'||ch[i][j+1]=='D')){//向右深搜 ch[i][j+1]='X'; sum++; find(i,j+1); ch[i][j+1]='.'; sum--; } } } } int main (){ int startrow,startline; while(~scanf("%d%d%d",&row,&line,&ttime),line,row,ttime){ sum=0; mark=0; for(int i=0;i<row;i++){ scanf("%s",ch[i]); } for(int i=0;i<row;i++) { for(int j=0;j<line;j++){ if(ch[i][j]=='D') doorrow=i,doorline=j; if(ch[i][j]=='S') startrow=i,startline=j; } } if( (ttime-(abs(doorrow-startrow)+abs(doorline-startline)))%2!=0)//奇偶剪枝 printf("NO "); else{ find(startrow,startline); printf("%s ",(mark==0?"NO":"YES")); } } return 0; }
本来感觉下面的在每一次深搜时都剪枝,应该比上面只在第一次的时候剪枝要优化,效率更高,但是提交时却时间更长,感觉应该是测试数据小的原因
第一种时间:
第二种时间:
代码:
#include <cstdlib> #include <cstring> #include <cmath> #include <cstdio> #include <iostream> using namespace std; int sum=1, row,line,doorrow,doorline,ttime,mark=0,startrow,startline;//mark为是否可行的标记 char ch[10][10]={0}; void find(int i,int j){ if(mark==1) return; if( (ttime-(abs(doorrow-startrow)+abs(doorline-startline)))%2!=0)//奇偶剪枝,且每次进入都剪枝 return ; if(sum==ttime&&doorrow==i&&doorline==j){ mark=1; } else if(sum<ttime&&mark==0){ if(i>0&&(ch[i-1][j]=='.'||ch[i-1][j]=='D')){ sum++; ch[i-1][j]='X'; find(i-1,j); ch[i-1][j]='.'; sum--; } if(mark==1) return ; if(i<row-1&&(ch[i+1][j]=='.'||ch[i+1][j]=='D')){ ch[i+1][j]='X'; sum++; find(i+1,j); ch[i+1][j]='.'; sum--; } if(mark==1) return ; if(j>0&&(ch[i][j-1]=='.'||ch[i][j-1]=='D')){ ch[i][j-1]='X'; sum++; find(i,j-1); ch[i][j-1]='.'; sum--; } if(mark==1) return ; if(j<line-1>0&&(ch[i][j+1]=='.'||ch[i][j+1]=='D')){ ch[i][j+1]='X'; sum++; find(i,j+1); ch[i][j+1]='.'; sum--; } if(mark==1) return ; } } int main (){ while(~scanf("%d%d%d",&row,&line,&ttime),line,row,ttime){ sum=0,mark=0; //别忘了每次都要初始化 for(int i=0;i<row;i++) scanf("%s",ch[i]); for(int i=0;i<row;i++) for(int j=0;j<line;j++){ if(ch[i][j]=='D') doorrow=i,doorline=j; if(ch[i][j]=='S') startrow=i,startline=j; } find(startrow,startline); printf("%s ",(mark==0?"NO":"YES")); } return 0; }