题目链接:点击打开链接
题目大意:
给你一个起点终点,另外告诉你每次可以走的方式,求从起点到终点的最小步数。达不到输出-1.
题意解析:
刚开始我看题上面有每次有能行走的范围,然后就建了个图裸搜了一下。然后mle,后来开始想解法,想到如果最短路径是围绕从起点到终点的一条直线,即总是交叉着这条直线走,把不符合要求的点去掉。那么符合要求的点就是点的到这条线段(注意是线段)的距离小于骑士一步能走的最大距离就好。想到了用map存坐标的做法。然后tle,我的妈,坐标的hash我不会写,我只会康托展开啊,不会了不会了,上网看题解,hash过程没怎么看懂,后来问了xh学长,稍微明白了一些。
哈希分为无损哈希和有损哈希,众所周知哈希是将一种状态转换为大数的形式保存下来。如果是无损哈希,那么就是保证每种状态得到的哈希值是确定的,即不可能出现多种状态对应一个hash值得情况。而有损hash一般是因为原状态如果转为无损hash空间过大无法保存而使用。但是为了保证正确性,需要引入邻接表或者说是链表的结构。将相同hash值 的状态用链表存起来,如果查到这个hash值就顺着链表一直找下去,也就是牺牲了时间来换取空间。不过听学长说mod的数要选好,不然不是tle就是mle,hash整个就是玄学。
另外还问了一下map的复杂度,大概是nlogn级别,而手写哈希基本是O(n)级别。。所以复杂度差的还是有点多的。以下贴代码部分,
对了,那个求点到线段的距离,因为点到直线有三种情况,它可能背离起点或者背离终点,所以不能单纯的用点到线段的距离来表示。要判断一下,刚开始想了好久不知道怎么判断,后来还是学数论的队友告诉我可以用三角形边长的性质来判断,而且看别人代码貌似背离的点可以直接舍掉,就稍微改了一下,也A了。应该是算是一些小优化吧。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <cstdlib>
#include <cmath>
#include <vector>
#include <queue>
#include <map>
#include <algorithm>
#include <set>
#include <functional>
using namespace std;
const int maxn=500000;
const int prime=999997;
int INF=1e9+7;
typedef long long LL;
typedef unsigned long long ULL;
int n,m,sx,sy,kx,ky,ans,A,B,C,idx;
double s,gg,a,b,c,ss;
int dx[20]; //题目要求的方向
int dy[20];
struct point //
{
int x,y;
int step;
};
struct node //构建有损hash
{
int x,y;
int next;
}edge[maxn];
int head[prime]; //存hash值
node v;
int hash(int x,int y) //得到每种坐标对应的hash值
{
return (((x<<15)^y)%prime+prime)%prime;
}
bool addedge(int key,int x,int y) //将hash值相同的保存起来
{
for(int i=head[key];i!=-1;i=edge[i].next)
{
if(edge[i].x==x&&edge[i].y==y)
return false;
}
edge[idx].x=x;
edge[idx].y=y;
edge[idx].next=head[key];
head[key]=idx++;
return true;
}
double dis(int a,int b,int p,int q)
{
return ((p-a)*(p-a)+(q-b)*(q-b));
}
bool check(int xx,int yy) //判断当前点是否符合剪枝要求
{
gg=sqrt(A*A+B*B);
a=dis(xx,yy,sx,sy);
b=dis(xx,yy,kx,ky);
c=dis(sx,sy,kx,ky);
if(a+c<b) //背离起点和终点的情况
return false;
else if(b+c<a)
return false;
else
ss=fabs(A*xx+B*yy+C*1.0)/gg;
if(ss<=s)
return true;
return false;
}
void bfs()
{
queue<point> que;
point st;
st.x=sx;
st.y=sy;
st.step=0;
que.push(st);
ans=-1;
addedge(hash(sx,sy),sx,sy);
while(!que.empty())
{
point k=que.front();
point kk;
que.pop();
if(k.x==kx&&k.y==ky)
{
ans=k.step;
break;
}
for(int i=0;i<m;i++)
{
int xx=k.x+dx[i];
int yy=k.y+dy[i];
v.x=xx;v.y=yy;
if(check(xx,yy)&&addedge(hash(xx,yy),xx,yy))
{
kk.x=xx;
kk.y=yy;
kk.step=k.step+1;
que.push(kk);
}
}
}
}
int main()
{
int QAQ;
scanf("%d",&QAQ);
while(QAQ--)
{
memset(head,-1,sizeof(head));
idx=0;
scanf("%d%d%d%d",&sx,&sy,&kx,&ky); //根据起点终点写出直线方程
A=sy-ky;
B=kx-sx;
C=sx*ky-kx*sy;
scanf("%d",&m);
s=0;
for(int i=0;i<m;i++)
{
scanf("%d%d",&dx[i],&dy[i]);
s=max(s,(double)sqrt(dx[i]*dx[i]+dy[i]*dy[i])); //刚开始忘写sqrt re了好多发
}
bfs();
if(ans==-1)
printf("IMPOSSIBLE\n");
else
printf("%d\n",ans);
}
}