POJ-3985:Knight's Problem(特殊的bfs剪枝以及hash)


题目链接:点击打开链接


题目大意:

给你一个起点终点,另外告诉你每次可以走的方式,求从起点到终点的最小步数。达不到输出-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);
    }
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值