搜索优化之 A* 与 IDA*
A*
构成
dijkstra 优先队列优化 \(+\) BFS \(+\) 玄学长度预估 \(=\) Astar。
解释
dijkstra 优先队列优化:每一次放进优先队列(小根堆)里的是 起点到这个状态的步数 \(+\) 当前点到终点的预估步数,在终点第一次出队时结束,此时得到的值必然为最小值(求第 \(k\) 短的路就出队 \(k\) 次)。
tip1: 每个点第一次入队所得出的步数不一定是初始状态到当前状态的最优解,所以一个状态可能被以前的状态更新多次,每次有走到新的状态时要判断当前方法是否比之前的方法更优,更优才把入队。
BFS:应该不用说吧 QWQ。
玄学长度预估:可以是曼哈顿距离(如八数码这题),甚至可以把终点变成起点,反着跑一遍 dijkstra(比如下下题),反正就是对这个状态还有几步可以达到最后状态的预估。
tip2: 预估值只能小于等于此状态到最终状态的真正步数。
例题
#include <bits/stdc++.h>
#define PIS pair <int, string>
#define PII pair <int, int>
#define LL long long
#define DB double
#define umap unordered_map
#define x first
#define y second
namespace FastIO
{
inline void read(int MOD, int &ret){
ret = 0;
char ch = getchar();int ngtv = 1;
if(MOD == 0) {while(ch < '0' || ch > '9'){if(ch == '-') ngtv = -1;ch = getchar();}while(ch >= '0' && ch <= '9'){ret = (ret << 3) + (ret << 1) + (ch ^ 48);ch = getchar();}}
else {while(ch < '0' || ch > '9'){if(ch == '-') ngtv = -1;ch = getchar();}while(ch >= '0' && ch <= '9'){ret = (((ret << 3) + (ret << 1)) % MOD + (ch ^ 48) % MOD) % MOD;ch = getchar();} }
ret *= ngtv;}
inline void cread(char &ch){ch = getchar();while(ch == ' ' || ch == '\n' || ch == '\r' || ch == 0) ch = getchar();}
}
using namespace FastIO;
using namespace std;
const int N = 1e6 + 10;
string start, seq;
umap <string, int> dist;
priority_queue <PIS, vector <PIS>, greater <PIS> > heap;
int d[2][4] = {{1, 0, 0, -1}, {0, -1, 1, 0}};
string op = "dlru", target = "123804765";
PII xi[9] = {{2, 2}, {0, 0}, {0, 1}, {0, 2}, {1, 2}, {2, 2}, {2, 1}, {2, 0}, {1, 0}};
int f(string s)
{
int res = 0, now;
for(int i = 0; i < 9; i ++ )
now = s[i] - '0', res += abs(xi[now].x - i / 3) + abs(xi[now].y - i % 3);
return res;
}
void bfs(string start)
{
dist[start] = 0;
heap.push({f(start), start});
while(heap.size())
{
auto t = heap.top();
heap.pop();
if(t.y == target)
{
printf("%d\n", dist[t.y]);
return ;
}
int x, y, xx, yy;
for(int i = 0; i < 9; i ++ )
if(t.y[i] == '0')
{
x = i / 3, y = i % 3;
break;
}
string source = t.y;
for(int i = 0; i < 4; i ++ )
{
xx = x + d[0][i], yy = y + d[1][i];
if(xx < 0 || xx >= 3 || yy < 0 || yy >= 3)
continue;
t.y = source;
swap(t.y[x * 3 + y], t.y[xx * 3 + yy]);
if(dist.count(t.y) == 0 || dist[t.y] > dist[source] + 1)
{
dist[t.y] = dist[source] + 1;
heap.push({dist[t.y] + f(t.y), t.y});
}
}
}
}
signed main()
{
char c;
for(int i = 0; i < 9; i ++ )
{
cread(c);
if(c != '0')
seq += c;
start += c;
}
int judge = 0;
for(int i = 0; i < 9; i ++ )
for(int j = i + 1; j < 9; j ++ )
if(seq[i] > seq[j])
judge ++ ;
bfs(start);
return 0;
}
#include <bits/stdc++.h>
#define PII pair <int, int>
#define PIII pair <PII, int>
#define x first
#define y second
#define LL long long
#define DB double
#define endl '\n'
namespace FastIO
{
inline void read(int MOD, int &ret){
ret = 0;
char ch = getchar();int ngtv = 1;
if(MOD == 0) {while(ch < '0' || ch > '9'){if(ch == '-') ngtv = -1;ch = getchar();}while(ch >= '0' && ch <= '9'){ret = (ret << 3) + (ret << 1) + (ch ^ 48);ch = getchar();}}
else {while(ch < '0' || ch > '9'){if(ch == '-') ngtv = -1;ch = getchar();}while(ch >= '0' && ch <= '9'){ret = (((ret << 3) + (ret << 1)) % MOD + (ch ^ 48) % MOD) % MOD;ch = getchar();} }
ret *= ngtv;}
inline void cread(char &ch){ch = getchar();while(ch == ' ' || ch == '\n' || ch == '\r' || ch == 0) ch = getchar();}
}
using namespace FastIO;
using namespace std;
const int N = 1010;
bool vis[N];
int n, m, s, t, k;
int u, v, w, dist[N], cnt[N];
vector <PII> v1[N], v2[N];
void dijkstra()
{
priority_queue <PII, vector <PII>, greater <PII> > q;
memset(dist, 0x3f, sizeof dist);
dist[t] = 0;
q.push({0, t});
vis[t] = 1;
while(q.size())
{
auto tt = q.top();
q.pop();
if(vis[tt.y]) continue;
vis[tt.y] = 1;
for(PII to : v2[tt.y])
{
if(tt.x + to.y < dist[to.x])
{
dist[to.x] = tt.x + to.y;
q.push({dist[to.x], to.x});
}
}
}
}
int Astar()
{
priority_queue <PIII, vector <PIII>, greater <PIII> > q;
q.push({{dist[s], 0}, s});
while(q.size())
{
auto tt = q.top();
q.pop();
int d = tt.x.y, node = tt.y;
cnt[node] ++ ;
if(cnt[t] == k)
return d;
for(auto to : v1[node])
if(cnt[to.x] < k)
q.push({{d + to.y + dist[to.x], d + to.y}, to.x});
}
return -1;
}
signed main()
{
read(0, n), read(0, m);
while(m -- )
{
read(0, u), read(0, v), read(0, w);
v1[u].push_back({v, w});
v2[v].push_back({u, w});
}
read(0, s), read(0, t), read(0, k);
if(s == t) k ++ ;
dijkstra();
printf("%d\n", Astar());
return 0;
}
IDA*
思想
在 DFS 时,如果当前深度 \(+\) 预估到达结束深度(\(\le\) 真实深度) \(\ge maxdepth\) 就跳出 DFS
与 A* 的区别
与 A*算法有异曲同工之妙,区别是 A*是 BFS 的剪枝,IDA* 是 DFS 的剪枝。
例题
Acwing 180 排书
洛谷
P10488 Booksort
#include <bits/stdc++.h>
#define PII pair <int, int>
#define LL long long
#define DB double
#define endl '\n'
namespace FastIO
{
inline void read(int MOD, int &ret){
ret = 0;
char ch = getchar();int ngtv = 1;
if(MOD == 0) {while(ch < '0' || ch > '9'){if(ch == '-') ngtv = -1;ch = getchar();}while(ch >= '0' && ch <= '9'){ret = (ret << 3) + (ret << 1) + (ch ^ 48);ch = getchar();}}
else {while(ch < '0' || ch > '9'){if(ch == '-') ngtv = -1;ch = getchar();}while(ch >= '0' && ch <= '9'){ret = (((ret << 3) + (ret << 1)) % MOD + (ch ^ 48) % MOD) % MOD;ch = getchar();} }
ret *= ngtv;}
inline void cread(char &ch){ch = getchar();while(ch == ' ' || ch == '\n' || ch == '\r' || ch == 0) ch = getchar();}
}
using namespace FastIO;
using namespace std;
const int N = 25;
int T, n, q[N], w[6][N];
int f()
{
// 由于每次交换只会影响三本书的后面位置,那最少就会交换当前不满足条件的书的本数 / 3(上取整)
int tot = 0;
for(int i = 0; i < n - 1; i ++ )
if(q[i] + 1 != q[i + 1]) tot ++ ;
// 等价于除与3上取整
return (tot + 2) / 3;
}
bool check()
{
// 判断当前情况是否满足题意(为升序)
for(int i = 0; i < n - 1; i ++ )
if(q[i] + 1 != q[i + 1]) return false;
return true;
}
bool dfs(int depth, int max_depth)
{
// IDA*剪枝
if(depth + f() > max_depth) return false;
if(check()) return true;
// 枚举去除书本的数量(区间长度)
for(int len = 1; len <= n; len ++ )
// 枚举取书的左端
for(int l = 0; l + len - 1 < n; l ++ )
{
int r = l + len - 1; // 计算右端
for(int i = r + 1; i < n; i ++ )
{
// 暂时将q储存在w[depth]中,方便回溯
memcpy(w[depth], q, sizeof q);
// 开始挪书
int y = l;
for(int x = r + 1; x <= i; x ++, y ++ ) q[y] = w[depth][x];
for(int x = l; x <= r; x ++ , y ++ ) q[y] = w[depth][x];
// 如果可以成功就无需继续枚举,直接返回
if(dfs(depth + 1, max_depth)) return true;
// 回溯
memcpy(q, w[depth], sizeof q);
}
}
// 无解
return false;
}
signed main()
{
read(0, T);
while(T -- )
{
read(0, n);
for(int i = 0; i < n; i ++ )
read(0, q[i]);
int depth = 0;
// 从小到大枚举最大深度
while(depth < 5 && !dfs(0, depth)) depth ++ ;
if(depth >= 5) puts("5 or more");
else printf("%d\n", depth);
}
return 0;
}