本文主要通过例题讲述搜索的几种优化方式,包括 DFS 的迭代加深,IDAstar,BFS 的优先队列优化,双向 BFS 以及 Astar。
一、广度优先搜索
1.1 双端队列 BFS
例题1 求第 k + 1 k+1 k+1 长边最短路。题目链接
由于边权 w ≤ 1 0 6 w\le 10^6 w≤106,可以二分得到第 k + 1 k+1 k+1 长边的最短可能长度。因此,本题转换成了求第 k + 1 k+1 k+1 长边长度为 l e n len len 的可能性。时间复杂度要求 O ( n log w ) O(n\log w) O(nlogw),因此需要在线性复杂度内判断可行性。
判断时,将所有边权 > l e n >len >len 的边权值设为 1 1 1, ≤ l e n \le len ≤len 的边权值设为 0 0 0,则 s → t s\to t s→t 的最短路若不超过 k k k,则代表只有不超过 k k k 条比其长的边,因此其可能成为 k + 1 k+1 k+1 长边。
在迭代时,使用双端队列储存所有遍历到的结点,若权值为 1 1 1 则从队尾入队,否则从队首入队,每次取队首进行扩展,即可得到 01 权图的最短路。
bool vis[maxn];
int dis[maxn],n,m,k,s,t;
vector<int> e[maxn],w[maxn];//模拟邻接表
bool check(ll len){
fill(dis,dis+n+10,inf);
memset(vis,0,sizeof(vis));//初始化
deque<int> q;//双端队列
dis[s]=0;q.push_back(s);
while(!q.empty()){
ll u=q.front();q.pop_front();
if(u==t) return dis[t]<=k;//到达汇点则返回可行性
if(vis[u]) continue;
vis[u]=1;
for(ll i=0;i<e[u].size();i++){
ll v=e[u][i];
if(!vis[v]){
ll c=(w[u][i]>len);//将边权转化为 01
if(dis[v]<=dis[u]+c) continue;
dis[v]=dis[u]+c;
if(c) q.push_back(v);
else q.push_front(v);//根据边权从队首或队尾入队
}
}
}
return 0;
}
ll Binary(ll l,ll r){
//二分答案
ll ans=inf;
while(l<=r){
ll mid=(l+r)>>1;
if(check(mid)) ans=mid,r=mid-1;
else l=mid+1;
}
return ans;
}
1.2 优先队列 BFS
对于每次扩展的代价不同时,采用优先队列(堆)优化迭代。每次取出当前代价最小的状态进行扩展,将新状态加入优先队列,反复迭代直至队列为空。
每个状态第一次被取出时,即为其最小代价,因此每个状态只扩展一次,复杂度为 O ( ( n + m ) log n ) O((n+m)\log n) O((n+m)logn),经典算法形如 Dijkstra。
例题:POJ 3635
有 n n n 个城市和 m m m 条道路构成无向图,每个城市有一个加油站,价格不同。每条道路的油耗即为其权值,求多询问,对于容量为 C C C 的车, s → t s\to t s→t 最少花多少钱。
使用拆点的思想,将每个点拆成 C C C 个数对 ( i d , f u e l ) (id,fuel) (id,fuel),表示 (编号,剩余油量),且 d i s [ i d ] [ f u e l ] dis[id][fuel] dis[id][fuel] 表示最少花费。 对于每次询问进行优先队列 BFS(即 Dijkstra),起始状态为 ( s , 0 ) (s,0) (s,0),每个状态课扩展出:
- 若 f u e l < C fuel<C fuel<C 可以加 1 1 1 升油,花费为 p r i c e i d price_{id} priceid。
- 若边权 w ( i d , v ) ≤ f u e l w(id,v)\le fuel w(id,v)≤fuel,则可以扩展到 ( v , f u e l − w ) (v,fuel-w) (v,fuel−w)。
不断取出花费最少的状态进行扩展,直到 t t t 被取出即可。复杂度 O ( ( n C + m ) log n C ) O((nC+m)\log nC) O((nC+m)lognC)。
int n,m,price[maxn];
int tot,ver[M],head[maxn],wth[M],nxt[M];
inline void add(int u,int v,int w){
ver[++tot]=v;wth[tot]=w;nxt[tot]=head[u];head[u]=tot;
ver[++tot]=u;wth[tot]=w;nxt[tot]=head[v];head[v]=tot;
}
struct node{
int d,u,c;
bool operator <(const node &x)const{
return d>x.d;
}
};
int dist[maxn][C];
bool vis[maxn][C];
inline int dijkstra(int s,int t,int cap){
memset(dist,0x3f,sizeof(dist));
priority_queue<node> q;
memset(vis,0,sizeof(vis));
q.push({
0,s,0});
while(!q.empty()){
node x=q.top();q.pop();
if(x.u==t) return x.d;
if(vis[x.u][x.c]==1) continue;
vis[x.u][x.c]=