第K短路
给定一张 N 个点(编号 1,2…N),M 条边的有向图,求从起点 S 到终点 T 的第 K 短路的长度,路径允许重复经过点或边。
注意: 每条最短路中至少要包含一条边。
输入格式
第一行包含两个整数 N 和 M。
接下来 M 行,每行包含三个整数 A,B 和 L,表示点 A 与点 B 之间存在有向边,且边长为 L。
最后一行包含三个整数 S,T 和 K,分别表示起点 S,终点 T 和第 K 短路。
输出格式
输出占一行,包含一个整数,表示第 K 短路的长度,如果第 K 短路不存在,则输出 −1。
数据范围
1≤S,T≤N≤1000,
0≤M≤104,
1≤K≤1000,
1≤L≤100
输入样例:
2 2
1 2 5
2 1 4
1 2 2
输出样例:
14
Astar算法和Dijkstra算法很像,Dijkstra算法是按dist[s]排序的,每次出队的时候确定最短距离,而Astar是按dist[s] + f(s)更新的,f(s) <= 到终点的真实距离。
f(s)等于0时就相当于用Dijkstra算法求第K短路,时间复杂度很高。
启发函数f(s)只是改变了状态搜索的顺序,优先搜索最有可能是答案的状态,从而减少搜索的状态数。f(s)与真实距离越接近越好。
假设起点为1,终点为4, 如果f(s)为0,会按1->2->4->3->4->4的顺序搜点,
如果f(s)为真实距离,会按1->4->2->4->3->4的顺序搜点,明显优化了一些,数据量越大优化越明显,第K次搜到4的时候就是第K短路。
另外题中说明最短路至少包含一条边,所以起点与终点重合时要搜第K + 1短路。
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define endl '\n'
using namespace std;
typedef pair<int, int> PII;
typedef pair<int, PII> PIII;
typedef long long ll;
const int N = 1010, M = 200010;
int n, m, S, T, K;
int h[N], rh[N], e[M], ne[M], w[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<PII, vector<PII>, greater<PII>>q;
memset(dist, 0x3f, sizeof dist);
dist[T] = 0;
q.push({0, T});
while(q.size())
{
PII t = q.top();
q.pop();
int ver = t.second, distance = t.second;
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];
//q.push({dist[j], j});
q.emplace(dist[j], j);
}
}
}
}
int astar()
{
priority_queue<PIII, vector<PIII>, greater<PIII>> q;
q.push({dist[S], {0, S}});
while(q.size())
{
PIII t = q.top();
q.pop();
int ver = t.second.second, distance = t.second.first;
cnt[ver] ++;
if(cnt[T] == K)return distance;
for(int i = h[ver]; i != -1; i = ne[i])
{
int j = e[i];
if(cnt[j] < K)
{
q.push({distance + w[i] + dist[j], {distance + w[i], j}});
}
}
}
return -1;
}
int main()
{
IOS
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 ++;
dijkstra();
int t = astar();
cout << t;
return 0;
}
每个点的f(s)用该点到终点的最短路替代,因为最短路肯定满足<=真实距离。
还有这种算法只适用于非负边权的情况。
八数码:
在一个 3×3 的网格中,1∼8 这 8 个数字和一个 X
恰好不重不漏地分布在这 3×3 的网格中。
例如:
1 2 3
X 4 6
7 5 8
在游戏过程中,可以把 X
与其上、下、左、右四个方向之一的数字交换(如果存在)。
我们的目的是通过交换,使得网格变为如下排列(称为正确排列):
1 2 3
4 5 6
7 8 X
例如,示例中图形就可以通过让 X
先后与右、下、右三个方向的数字交换成功得到正确排列。
交换过程如下:
1 2 3 1 2 3 1 2 3 1 2 3
X 4 6 4 X 6 4 5 6 4 5 6
7 5 8 7 5 8 7 X 8 7 8 X
把 X
与上下左右方向数字交换的行动记录为 u
、d
、l
、r
。
现在,给你一个初始网格,请你通过最少的移动次数,得到正确排列。
输入格式
输入占一行,将 3×3 的初始网格描绘出来。
例如,如果初始网格如下所示:
1 2 3
x 4 6
7 5 8
则输入为:1 2 3 x 4 6 7 5 8
输出格式
输出占一行,包含一个字符串,表示得到正确排列的完整行动记录。
如果答案不唯一,输出任意一种合法方案即可。
如果不存在解决方案,则输出 unsolvable
。
输入样例:
2 3 4 1 5 x 7 6 8
输出样例
ullddrurdllurdruldr
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define endl '\n'
#define x first
#define y second
using namespace std;
typedef pair<int, int> PII;
typedef pair<char, string> PCS;
typedef pair<int, string> PIS;
typedef long long ll;
string start;
bool check(int x, int y)
{
if(x < 0 || x >= 3 || y < 0 || y >= 3)return false;
return true;
}
int f(string s)
{
int res = 0;
for(int i = 0; i < 9; i ++)
{
if(s[i] == 'x')continue;
int t = s[i] - '1';
res += abs(i / 3 - t / 3) + abs(i % 3 - t % 3);
}
return res;
}
string bfs()
{
string end = "12345678x";
unordered_map<string, int>dist;
dist[start] = 0;
unordered_map<string, pair<string, char>>last;
priority_queue<PIS, vector<PIS>, greater<PIS>>q;
q.push({f(start), start});
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
char cs[4] = {'u', 'r', 'd', 'l'};
while(q.size())
{
auto t = q.top();
q.pop();
string state = t.second;
string tmp = state;
int distance = dist[state];
if(state == end)break;//A*算法用的是优先队列,时间复杂度里多了个 O(logn),所以需要提前退出。
int x, y;
for(int i = 0; i < 9; i ++)
{
if(state[i] == 'x')
{
x = i / 3, y = i % 3;
break;
}
}
for(int i = 0; i < 4; i ++)
{
int nx = x + dx[i], ny = y + dy[i];
if(!check(nx, ny))continue;
swap(state[x * 3 + y], state[nx * 3 + ny]);
if(!dist.count(state) || dist[state] > distance + 1)
//A*算法,除了终点之外,当第一次搜到某个点的时候,距离不一定是最短的,
//其dist[]的值是不断变小的,所以要加后一个判断。
{
dist[state] = distance + 1;
last[state] = {tmp, cs[i]};
q.push({dist[state] + f(state), state});
}
swap(state[x * 3 + y], state[nx * 3 + ny]);
}
}
string ans;
while(end != start)
{
ans.push_back(last[end].y);
end = last[end].x;
}
reverse(ans.begin(), ans.end());
return ans;
}
int main()
{
IOS
char op[2];
string str;
for(int i = 0; i < 9; i ++)
{
cin >> op;
start.push_back(op[0]);
if(op[0] != 'x')str.push_back(op[0]);
}
int res = 0;
for(int i = 0; i < str.size(); i ++)
{
for(int j = i + 1; j < str.size(); j ++)
{
if(str[i] > str[j])res ++;
}
}
if(res % 2)cout << "unsolvable";
else cout << bfs();
return 0;
}
注意要提前退出,不然会超时,理由是优先队列带了一个logn的复杂度。
九个字母排列组合最多362800中情况,再带上一个log2(362800)差不多5986338次搜索,其中每次搜索还要循环(9 + 4)次,粗略计算共77822397次,接近1e8次,所以要提前退出。
A*算法,除了终点之外,当第一次搜到某个点的时候,距离不一定是最短的,所以更新距离时要注意多加一个条件。
题目来源:Acwing