本题思路:A* 应用场景:起点→终点的最短距离,状态空间 >> 1e10,启发函数减小搜索空间A*算法:while(q.size())t ← 优先队列的队头 (建小根堆的方式进行求解) 当终点第一次出队时 break;从起点到当前点的真实距离 d_real,从当前点到终点的估计距离 d_estimate选择一个估计距离最小的点 min(d_estimate),for j in ne[t]:将邻边入队
A*算法条件:
估计距离<=真实距离d[state] + f[state] = 起点到state的真实距离 + state到终点的估计距离=估计距离
d[state] + g[state] = 起点到state的真实距离 + state到终点的真实距离=真实距离一定是有解才有 d[i] >= d[最优] = d[u]+f[u],f[u] >= 0。
证明终点第一次出队列即最优解
1 假设终点第一次出队列时不是最优
则说明当前队列中存在点u
有 d[估计]< d[真实]
d[u] + f[u] <= d[u] + g[u] = d[队头终点]
即队列中存在比d[终点]小的值,
2 但我们维护的是一个小根堆,没有比d[队头终点]小的d[u],矛盾
A* 不用判重
以边权都为1为例
A o→o→o
↑ ↓
S o→o→o→o→o→o→o T
B
dist[A] = dist[S]+1 + f[A] = 7
dist[B] = dist[S]+1 + f[B] = 5
则会优先从B这条路走到T
B走到T后再从A这条路走到T
#include
#define x first
#define y second
using namespace std;
typedef pair PII;
typedef pair PIII;
const int N = 1010, M = 200010;
int n, m, S, T, K;
int h[N], rh[N], e[M], w[M], ne[M], idx;
int dist[N], cnt[N];
bool st[N];
void add(int h[],int a,int b,int c)
{
e[idx] = b;
w[idx] = c;
ne[idx] = h[a];
h[a] = idx++;
}
void dijkstra()
{
priority_queue,greater> heap;
heap.push({0,T});//终点
memset(dist, 0x3f, sizeof dist);
dist[T] = 0;
while(heap.size())
{
auto t = heap.top();
heap.pop();
int ver = t.y;
if(st[ver]) continue;
st[ver] = true;
for(int i=rh[ver];i!=-1;i=ne[i])
{
int j = e[i];
if(dist[j]>dist[ver]+w[i])
{
dist[j] = dist[ver] + w[i];
heap.push({dist[j],j});
}
}
}
}
int astar()
{
priority_queue, greater> heap;
// 谁的d[u]+f[u]更小 谁先出队列
heap.push({dist[S], {0, S}});
while(heap.size())
{
auto t = heap.top();
heap.pop();
int ver = t.y.y,distance = t.y.x;
cnt[ver]++;
//如果终点已经被访问过k次了 则此时的ver就是终点T 返回答案
if(cnt[T]==K) return distance;
for(int i=h[ver];i!=-1;i=ne[i])
{
int j = e[i];
/*
如果走到一个中间点都cnt[j]>=K,则说明j已经出队k次了,且astar()并没有return distance,
说明从j出发找不到第k短路(让终点出队k次),
即继续让j入队的话依然无解,
那么就没必要让j继续入队了
*/
if(cnt[j] < K)
{
// 按 真实值+估计值 = d[j]+f[j] = dist[S->t] + w[t->j] + dist[j->T] 堆排
// 真实值 dist[S->t] = distance+w[i]
heap.push({distance+w[i]+dist[j],{distance+w[i],j}});
}
}
}
// 终点没有被访问k次
return -1;
}
int main()
{
cin >> m >> n;
memset(h,-1,sizeof h);
memset(rh,-1,sizeof rh);
for(int i=0;i> a >> b >> c;
add(h,a,b,c);
add(rh,b,a,c);
}
//本题可以建反向边的方式
cin >> S >> T >> K;
// 起点==终点时 则d[S→S] = 0 这种情况就要舍去 ,总共第K大变为总共第K+1大
if (S == T) K ++ ;
// 从各点到终点的最短路距离 作为估计函数f[u]
dijkstra();
cout << astar();
return 0;
}
本题思路:采用AcWing 179. 对八数码问题的一些补充 - AcWing
#include
#define x first
#define y second
int f(std::string state)
{
int dist=0;
for(int i=0;i<9;i++)
if(state[i]!='x'){
int t=state[i]-'1';//计算下标
dist=dist+abs(i/3-t/3)+abs(i%3-t%3);//曼哈顿距离
}
return dist;
}
std::string bfs(std::string start)
{
std::string end="12345678x";
std::unordered_map d;
std::priority_queue,std::vector>,std::greater>> heap;
std::unordered_map> last;//存储一个元素由哪种状态,经过哪种操作得来,跟前面几题一样
heap.push({f(start),start});//加入起点
d[start]=0;//起点到起点的距离为0
//要将操作数组与坐标变化数组一一对应
char oper[]="udlr";
int dx[4]={-1,1,0,0},dy[4]={0,0,-1,1};
while(heap.size())
{
auto t=heap.top();//队头
heap.pop();//弹出
std::string state=t.y;//记录
if(t.y==end) break;//终点出列的话就退出
int x,y;//查找x的横纵坐标
for(int i=0;i<9;i++)
if(state[i]=='x')
{
x=i/3,y=i%3;
break;
}
std::string init=state;
for(int i=0;i<4;i++)
{
int a=x+dx[i],b=y+dy[i];
if(a<0||a>=3||b<0||b>=3) continue;//越界就跳过
std::swap(state[a*3+b],state[x*3+y]);//交换下标位置
if(!d.count(state)||d[state]>d[init]+1)//如果没有被记录或者小于记录值
{
d[state]=d[init]+1;//更新距离
heap.push({f(state)+d[state],state});//加入堆中
last[state]={init,oper[i]};//标记由哪种状态转移而来,并且记录执行的操作
}
state=init;//因为要扩展到四个方向,所以要还原
}
}
std::string ans;
//跟前面几题原来相同
while(end!=start)
{
ans+=last[end].y;
end=last[end].x;
}
reverse(ans.begin(),ans.end());//将其反转
return ans;
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);std::cout.tie(nullptr);
std::string start,x;
char c;
while(std::cin>>c){//忽略空格
start+=c;
if(c!='x') x+=c;
}
//八数码判断条件之一:如果逆序数为奇数则说明此时无解
int res=0;
for(int i=0;i<8;i++)
for(int j=i+1;j<8;j++)
if(x[i]>x[j]) res++;
if(res%2) std::cout<<"unsolvable"<