目录
(一)A*
1)第K短路(困难)
A*算法介绍:
2)八数码
(二)DFS之搜索顺序
3) 分成互质组
样例解释:
第一行输入 n个点,m条边
接下来m行输入 A->B的 有向边 边权为C
最后一行输入 起点S 终点T 的第K条最短路
特殊情况分析
参考自:AcWing 178. 第K短路(A* 反向计算最短路作为到终点的估计值) - AcWing
数学证明我是真的不会,但 我可以学
(一)应用场景:
这道题就是A* 算法的模板题目,注意它的特殊之处,每个点可以被重复走过,那么这就意味着起点和终点可以重复走过,具体可看上面的样例图解
(二)暴力思想:
我的暴力想法是:从起点开始,将距离起点最近的点加入至一个集合当中,然后再从集合中依次扩展距离它们的最短距离的点,然后搞一个计数的,谁先到达终点谁就是最短的,每有一个到达中终点,计数器++,那么就证明它是第cnt短的,当然我没有任何的证明佐证这个是正确的
(三)算法优化:
核心思想--->>> A* 反向计算最短路作为到终点的估计值
我来说代码的流程把.....
预处理阶段
A*解决阶段
#include
#include
#include
#include
#include
using namespace std;
#define x first
#define y second
typedef pair PII;
typedef pair PIII;
const int N = 1010;//点数
const int M = 2e5 + 10;//边数
int n, m, S, T, K;//点数,边数,起点,终点,第K小
int h[N], rh[N], e[M], w[M], ne[M], idx;//正向 h[] 反向 rh[]
int dist[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++;
}
// 在反向图上dijkstra(),保存估价函数(dist[]距离)
// dist存的是该点到终点的最小距离
void dijkstra()
{
priority_queue, greater >heap;
heap.push({ 0,T });//<距离,编号> -->> 终点到终点的距离初始化为0
memset(dist, 0x3f, sizeof dist);
dist[T] = 0;// 终点到终点的距离初始化为0
while (heap.size())
{
auto t = heap.top();
heap.pop();
int ver = t.y;//取出编号
if (st[ver]) continue;
st[ver] = true;
// 在反向图上遍历,rh[]数组
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;
//A* 算法,从起点开始搜
heap.push({ dist[S],{0,S} });//<估价值,<真实值,编号> >
int cnt = 0;//终点被遍历了几次
if (dist[S] == 0x3f3f3f3f) return -1;//终点到起点的距离为INF,那么就证明无解,返回-1
// 谁的d[u]+f[u]更小 谁先出队列:估价函数的作用
while (heap.size())
{
auto t = heap.top();
heap.pop();
int ver = t.y.y, distance = t.y.x;//编号---从起点到该点的真实距离
if (ver == T) cnt++;//遍历一遍终点,cnt++,直到第K次
if (cnt == K) return distance;//这个点是终点,并且是第k短路,直接返回第k短路的真实距离distance
// 正向扩展所有的边
// 用 起点到该点的真实距离+ 该点到终点的估价距离来 作为标准
// distance + w[i] + dist[j]
for (int i = h[ver]; i != -1; i = ne[i])
{
int j = e[i];
heap.push({ distance + w[i] + dist[j],{distance + w[i], j} });
}
}
return -1;
}
int main()
{
cin >> n >> m;
//初始化表头
memset(h, -1, sizeof h);
memset(rh, -1, sizeof rh);
for (int i = 0; i < m; i++)
{
int a, b, c;
cin >> a >> b >> c;
add(h, a, b, c);
add(rh, b, a, c);
}
cin >> S >> T >> K;
if (S == T) K++;//特判:如果起点和终点一样,那么就将第K小的变为第K+1小的:目的是跳过该情况
dijkstra();//堆优化版迪杰特拉预处理-->>该点到终点的距离-->>估价函数(dist距离)
cout << astar() << endl;//ans
return 0;
}
这个我的注释绝对已经可以无障碍看懂了
#include
#include
#include
#include
#include
#define x first
#define y second
using namespace std;
typedef pair PIS;
int f(string m)//估计函数
{
int dt=0;//计算该string的方案得到的距离
for(int i=0;i<9;i++)//这里1~8对应的下标为0~7
{
if(m[i]!='x')//跳过'x'
{
int t=m[i]-'1';//对应下标
//行:i/3 列:i%3
dt=dt+abs(i/3-t/3)+abs(i%3-t%3);//曼哈顿距离
}
}
return dt;//返回总曼哈顿距离
}
string bfs(string start)
{
string end="12345678x";//终点
unordered_map d;//存储距离
priority_queue, greater> heap;//小根堆,将元素的估计终点距离从小到大排序
unordered_map> last;//存储一个元素由哪种状态,经过哪种操作得来,跟前面几题一样
heap.push({f(start),start});//<估价距离,对应的string> --->>加入起点
d[start]=0;//起点到起点的距离为0
//要将操作数组与坐标变化数组一一对应
char oper[]="udlr";//操作ans字符数组
int dx[4]={-1,1,0,0};
int dy[4]={0,0,-1,1};//方位实际数组
while(heap.size())
{
auto t=heap.top();//队头//取出编号idx->>string
heap.pop();//弹出
string state=t.y;//记录
if(t.y==end) break;如果该编号已经是end状态的编号-->>达到理想情况-->>break跳出
int x,y;//找到'x'的坐标
for(int i=0;i<9;i++)
if(state[i]=='x')
{
x=i/3,y=i%3;
break;
}
string init=state;//存储代替
for(int i=0;i<4;i++)//以'x'的坐标(x,y)为源点 进行bfs
{
int a=x+dx[i],b=y+dy[i];
if(a<0||a>=3||b<0||b>=3) continue;//越界就跳过
swap(state[a*3+b],state[x*3+y]);//扩展出来的(a,b) 与 扩展之前的(x,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;//因为要扩展到四个方向,所以要还原
}
}
string ans;
//跟前面几题原来相同
while(end!=start)
{
ans+=last[end].y;
end=last[end].x;
}
reverse(ans.begin(),ans.end());//将其反转
return ans;
}
int main()
{
string start,x,c;
while(cin>>c)//这样输入可以忽视空格
{
start+=c;//start保存 有x的串
if(c!="x") x+=c;//x保存 没有x的串
}
int res=0;//统计逆序对的数量
for(int i=0;i<8;i++)
for(int j=i+1;j<8;j++)
if(x[i]>x[j])
res++;
//末状态为:1 2 3 4 5 6 7 8 x 逆序对的数量为 0
//行的交换不会改变逆序对的数量
//列的交换会改变+2 或 -2 的逆序对的数量变化
//可以手动模拟:那么即可知道(以下的结论)
if(res%2) printf("unsolvable\n");//如果逆序对为奇数,就不可能抵达终点
else cout<
前两题是马走日 和 单词接龙 这里放个链接就不写了
[AcWing算法刷题]之DFS+BFS迷宫模板(简单)_lihua777的博客-CSDN博客
#include
#include
#include
using namespace std;
const int N = 10;
int a[N];
vector g[N];
int n;
int ans = 10;
int len;
int gcd(int a, int b)
{
return b ? gcd(b, a % b) : a;
}
bool check(int c, int x)
{
for (int i = 0; i < g[c].size(); i++)
{
if (gcd(g[c][i], x) > 1) return false;
}
return true;
}
void dfs(int u)
{
if (u == n)
{
ans = min(ans, len);
return;
}
//每个元素的方法即——放到当前已经存在的组中 或者 放到新开的组中
for (int i = 0; i < len; i++)
{
if (check(i, a[u]))
{
g[i].push_back(a[u]);
dfs(u + 1);
g[i].pop_back();
}
}
//可见这里的len代表着的是当前开辟数组的个数
g[len++].push_back(a[u]);
dfs(u + 1);
g[--len].pop_back();
}
int main()
{
cin >> n;
for (int i = 0; i < n; i++) cin >> a[i];
dfs(0);
cout << ans << endl;
return 0;
}