题目の传送门
都成了一道模板题了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就直接爆零滚粗了(ノ`Д)ノ