• 【模板篇】k短路 SDOI2010 魔法猪学院


    题目の传送门

    都成了一道模板题了OvO

    ==============================================================================

    吐槽时间

    不想看的自行点目录

    今天才发现自己还没有学A*
    就去看了一下A*寻路, 但是只看也不行啊, 得找题练一练啊..
    然后上luogu搜A*算法结果找到了这道题?
    但这不应该是道图论么= =
    然后看了看题解发现原来是最短路预处理然后A*
    那就写嘛, 写的有模有样的大约30min?
    然后过了样例, 交上去30pts, 其余RE..
    然后改数组大小再交 40pts MLE…
    蛤? 然后又改一遍, 40pts RE…
    什么鬼咯… 然后发现自己根本tm没调用dij()这个函数…
    也就是没预处理, 直接搜的, 突然觉得40pts很不错了2333
    然后调用dij()就开始恒输出0了. 结果发现刚开始要反向建边?
    然后就xjb改, 因为怕MLE还删掉一些数组重复利用之类的…
    后来终于又过了样例 然后交上去0pts 9WA1RE…
    Emmmm又是什么锅啊…
    冷静分析了一波, 发现自己dij往里加点的时候是这么加的:

    hp[sz++]=(zt){d[e[i].to],0,d[e[i].to]};

    这tm还能过样例??????????
    然后改完就能70pts RE了.
    然后改两波数组大小就终于AC了.
    然而要是省选干出这种事就死透了.. 最多通过对拍搞个70吧? (没准直接M掉滚粗了Emmmm

    ==============================================================================

    题目分析

    好了吐槽时间结束, 我们来看看这道题.
    都说了是k短路模板, 题意就是求1->n的前k短路的长度和不超过m, 求最大的k.
    我们就进行bfs, 第k次搜到n搜到的就是最短路.
    但是朴素的bfs很显然是过不了的, 我们要采用启发式搜索(A*).

    感觉A*好像比数论什么的要有用... 现实生活中用到A*的场合也是非常多的, 比如游戏中角色的自动寻路啊之类的都要用到A*不是...(然后再吐槽一句在markdown里打多个A*的话会出现玄学的斜体...

    A*的基本形式: f(x)=g(x)+h(x), 其中
    - f(x)表示从起始状态到结束状态的估价
    - g(x)表示从起始状态到当前状态已经花费的代价.
    - h(x)自然就是表示从当前状态到结束状态的估价咯~

    其中A*算法的核心就是一个合适的h(x)的选择.
    - 如果h(x)比实际值要小, 那么能得到最优解, 但是搜索范围较大, 效率较低.
    - 如果h(x)比实际值要大, 那么搜索范围较小, 速度快, 但不一定能取得最优解.
    - 而如果h(x)恰好等于实际值, 那么搜索将按照最优解进行, 是我们最想看到的一种情况.
    - 所以我们要尽可能让自己选的h(x)接近实际值.

    很幸运的是, 在k短路这个问题中, 我们刚好能取到完美地等于实际值的h(x),
    我们只需要把边反向, 从终点开始求一波单源最短路就ok了.. (而且好像这题不卡spfa…

    A*的过程中总是要找到f值最小的优先进行扩展, 我们就写个堆就好了..
    这里倾情推荐algorithm库里的push_heap和pop_heap函数, 不管开不开O2都快的飞起, 比手写堆都快, 不知道怎么实现的.
    (而且这题据说用priority_queue会MLE的说~
    我们每次搜到n就用E减去这条路的长度, 如果E<0就输出当前的k就好了.

    怎么说呢 这题还不能算是特别正宗的A*, 因为也不需要记录什么openset closeset, 也不需要输出路径所以不需要记录父亲之类的, 所以就这样吧…

    代码

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    const int N=5050;
    const int M=2e5+8;
    struct edge{int to,next;double data;}e[M]; int v[N],tot;
    inline void buildedge(const int&x,const int&y,const double&z){
        e[++tot].to=y;e[tot].data=z;e[tot].next=v[x];v[x]=tot;
    }
    struct zt{double f,g;int x;}hp[M<<3]; int sz;
    inline bool operator<(const zt&a,const zt&b){return a.f>b.f;}
    double d[N],Z[M],K; int n,m,X[M],Y[M];
    void dij(){
        for(int i=1;i<n;++i) d[i]=1e12;
        d[n]=0; hp[sz++]=(zt){0,0,n};
        while(sz){
            ::pop_heap(hp, hp+sz); zt x=hp[--sz];
            if(d[x.x]<x.f) continue;
            for(int i=v[x.x];i;i=e[i].next)
                if(d[x.x]+e[i].data<d[e[i].to]){
                    d[e[i].to]=d[x.x]+e[i].data;
                    hp[sz++]=(zt){d[e[i].to],0,e[i].to};
                    ::push_heap(hp, hp+sz);
                }
        }
    }
    int Astar(int s=0){
        hp[sz++]=(zt){d[1],0,1};
        while(sz){
            ::pop_heap(hp, hp+sz); zt x=hp[--sz];
            if(x.f>K) break;
            for(int i=v[x.x];i;i=e[i].next){
                hp[sz++]=(zt){x.g+e[i].data+d[e[i].to],x.g+e[i].data,e[i].to};
                ::push_heap(hp, hp+sz);
            }
            if(x.x==n){
                K-=x.f; if(K<0) break; ++s;
            }
        }
        return s;
    }
    int main(){
        scanf("%d%d%lf",&n,&m,&K);
        for(int i=1;i<=m;++i){
            scanf("%d%d%lf",&X[i],&Y[i],&Z[i]);
            ::buildedge(Y[i], X[i], Z[i]);
        } dij();
        ::memset(e, 0, sizeof e);
        ::memset(v, 0, sizeof v); tot=0;
        for(int i=1;i<=m;++i)
            ::buildedge(X[i], Y[i], Z[i]);
        printf("%d",Astar());
    }

    以后数组算着空间够的话数组一定能开大就开大….
    当然还是一定要避免MLE… 毕竟数组开小了还能得个部分分, MLE就直接爆零滚粗了(ノ`Д)ノ

  • 相关阅读:
    Philosophy is systematic reflective thinking on life.
    HashMap与HashTable的区别、HashMap与HashSet的关系
    android Intent机制详解
    Android Parcelable理解与使用(对象序列化)
    Java并发编程:volatile关键字解析
    JavaEE 对象的串行化(Serialization)
    pytorch学习
    numpy的一些用法
    约瑟夫问题
    双向链表及其操作
  • 原文地址:https://www.cnblogs.com/enzymii/p/8412098.html
Copyright © 2020-2023  润新知