Astar 第K短路 + 八数码

第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 与上下左右方向数字交换的行动记录为 udlr

现在,给你一个初始网格,请你通过最少的移动次数,得到正确排列。

输入格式

输入占一行,将 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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值