A*
ps:作者是蒟蒻,可能有些地方是错误的,欢迎批评指正。
思想
走迷宫怎么走?用BFS就可以,但是BFS需要把所有步数<=答案的节点全部入队。
我们会发现这样遍历有很多没用的节点也入队了,能不能让这些节点不入队?这里有一个想法,就是只让最接近T(曼哈顿距离小)的节点入队,那么就变成这样了:
好像很对?其实是错的,因为迷宫可以有障碍,一旦有障碍,这个想法就会被毙掉。
黑色的是障碍,会发现S到不了T。
(其实前面都是free话)
A*的想法是,让不是最“接近T”(有了障碍物之后曼哈顿距离不一定是实际距离,所以加双引号)的节点也能入队,但是最“接近T”的节点先用来遍历,这样既可以减少入队数(虽然入队数还是可能很大,但是肯定小了很多),又可以解决之前的问题。A*也可以用来求边权不同的最短路,只要让节点能重复入队即可。
上述“接近T”被称为估价函数,视不同题目而定(比如走迷宫就可以用曼哈顿距离)格式如下:
f(n)=g(n)+h(n)
f(n)表示经过n并到达T的估计代价。
g(n)表示从S到n的实际代价。
h(n)表示从n到T的估计代价(启发式函数)。
那么f(n)越符合题意A*就越先用来遍历。
A*是一种启发式搜索,复杂度玄学,h(n)越接近实际代价,效率越高。
需要注意的是,由于f(n)只是估计代价,所以有时先到T的不一定的是真正最优的,所以要从所有到T的状态中刷出最优解才是答案(除非h(n)就是实际代价,那就没必要刷出最优解了)。
K短路
思想
最短路比较简单,但次短路就很难了,常用最短路方法无法派上用场,更恶心的是还有K短路。
其实K短路能用A*轻松解决!我们知道A*会让最“优先”的节点先遍历,所以先到T的路径常常比较短。而启发式函数h(n)我们是可以做到实际代价的:用最短路算法预处理每一个点到T的距离。这样A*每一步都是最优秀的,也就是说当第K次走到T时,就是K短路。
因为启发式函数是最好的,所以效率不错,但时间和空间复杂度还是玄学(=_=|||)。
模板
以POJ2449为例。
完全裸的K短路,只不过这道题有毛病,如果s==t不走不算一种方案,所以s==t时K要+1。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int maxn=1000,maxm=100000,maxk=1000;
int n,m,st,gl,K,tot;
int E[2],lnk[2][maxn+5],nxt[2][maxm+5],son[2][maxm+5],w[2][maxm+5];
int h[maxn+5],que[maxn+5];
bool vis[maxn+5];
struct data
{
int x,g;
data(int X=0,int G=0) {x=X;g=G;}
bool operator < (const data &a) const {return g+h[x]>a.g+h[a.x];}
};
priority_queue<data> heap_s; //用优先队列来保证f小的先用来遍历
bool Eoln(char ch) {return ch==10||ch==13||ch==EOF;}
int readi(int &x)
{
int tot=0,f=1;char ch=getchar(),lst='+';
while ('9'<ch||ch<'0') {if (ch==EOF) return EOF;lst=ch;ch=getchar();}
if (lst=='-') f=-f;
while ('0'<=ch&&ch<='9') tot=tot*10+ch-48,ch=getchar();
x=tot*f;
return Eoln(ch);
}
void Add(int id,int x,int y,int z)
{
son[id][++E[id]]=y;w[id][E[id]]=z;
nxt[id][E[id]]=lnk[id][x];lnk[id][x]=E[id];
}
void Spfa() //Spfa预处理h(n)
{
memset(vis,0,sizeof(vis));memset(h,63,sizeof(h));
int Head=0,Tail=0;que[++Tail]=gl;vis[gl]=true;h[gl]=0;
while (Head!=Tail)
{
int x=que[Head=(Head+1)%maxn];vis[x]=false;
for (int j=lnk[1][x];j;j=nxt[1][j])
if (h[x]+w[1][j]<h[son[1][j]])
{
h[son[1][j]]=h[x]+w[1][j];
if (!vis[son[1][j]])
{
que[Tail=(Tail+1)%maxn]=son[1][j];vis[son[1][j]]=true;
if (h[que[Tail]]<h[que[(Head+1)%maxn]])
swap(que[Tail],que[(Head+1)%maxn]);
}
}
}
}
void A_star()
{
while (!heap_s.empty()) heap_s.pop();
heap_s.push(data(st,0));
while (!heap_s.empty())
{
data x=heap_s.top();heap_s.pop();
if (x.x==gl&&++tot==K) {printf("%d\n",x.g);break;} //第K次
for (int j=lnk[0][x.x];j;j=nxt[0][j])
heap_s.push(data(son[0][j],x.g+w[0][j]));
}
}
int main()
{
freopen("program.in","r",stdin);
freopen("program.out","w",stdout);
readi(n);readi(m);
for (int i=1;i<=m;i++)
{
int x,y,z;readi(x);readi(y);readi(z);
Add(0,x,y,z);Add(1,y,x,z);
}
readi(st);readi(gl);readi(K);if (st==gl) K++;
Spfa();A_star();
if (tot<K) printf("-1\n");
return 0;
}