【洛谷】P4467 [SCOI2007] k短路

【题目链接】P4467 [SCOI2007] k短路 - 洛谷 (luogu.com.cn)

【A*算法】模板代码,未解决 “简单路径” 问题。

#include<bits/stdc++.h>
using namespace std;

const int MAXN = 5005;
const int MAXM = 200005;
const int INF = 1e9;

// 边结构体
struct Edge {
    int to;
   	int w;
};

vector<Edge> graph[MAXN];      // 原图
vector<Edge> rev_graph[MAXN];  // 反向图

int dist[MAXN]; // h(n): 从节点n到终点N的最短距离
int N, M , K, A , B;

// Dijkstra 预处理 h(n)
void dijkstra(int start) {
    for (int i = 1; i <= N; ++i)  dist[i] = INF;
    dist[start] = 0;
    // 小根堆: pair<距离, 节点>
    priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>> > pq;
    pq.push({0, start});
    while(pq.size()) {
        auto [d, u] = pq.top(); pq.pop();
        if (d > dist[u]) continue;
        for (auto& e : rev_graph[u]) {
            if (dist[e.to] > dist[u] + e.w) {
                dist[e.to] = dist[u] + e.w;
                pq.push({dist[e.to], e.to});
            }
        }
    }
}

// A* 搜索的状态
struct State {
    int node;       // 当前节点编号 
    int g_cost;  // 从起点到当前节点的实际代价
    int f_cost;  // 估价函数 f = g + h
   	string path; // 具体路径 
    bool operator>(const State& other) const {
    	if( f_cost != other.f_cost ) return f_cost > other.f_cost;
    	return path > other.path;
    }
};

string astar_kth_shortest_count(int start) {
    // 如果起点无法到达终点,直接返回0
    if (dist[start] >= INF) return "NO";

    priority_queue<State, vector<State>, greater<State> > pq;
    
    // 初始状态:起点1,g=0, f=h(1)
    pq.push({start, 0, dist[start] , to_string(start)});

    int count = 0;

    while( pq.size() ) {
        State curr = pq.top(); pq.pop(); 
        if (curr.node == B) {
            ++count;
            // 找到一条从 A 到 B 的路径,长度为 curr.g_cost
             cout << curr.path << endl;//第count条路径
            if ( count==K ) {
                return curr.path ;
            }
        }
        // 扩展邻居
        for (auto& e : graph[curr.node]) {
            int next_node = e.to;
            int next_g = curr.g_cost + e.w;
            string next_path = curr.path + "-" + to_string( next_node ) ; 
            // 只有当 next_node 能到达终点时才加入队列 (dist[next_node] != INF)
            if (dist[next_node] < INF) {
                int next_f = next_g + dist[next_node];
                pq.push({next_node, next_g, next_f , next_path});
            }
        }
    }
    return "NO";
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(NULL);

	cin>>N>>M>>K>>A>>B; 
    for (int i = 0; i < M; ++i) {
        int u, v, w;
        cin >> u >> v >> w;
        graph[u].push_back({v, w});
        rev_graph[v].push_back({u, w}); // 反向建图
    }

    // 1. 预处理 h(B):在反向图上跑 Dijkstra,求所有点到 B 的最短距离
    dijkstra(B);

    // 2. A* 搜索求 K 短路的路径 
    string ans = astar_kth_shortest_count(A);

    cout << ans << endl;
    return 0;
}

  

【简单路径】

要保证K短路径是‌简单路径‌(即路径中不含重复顶点、无环),核心做法是在搜索扩展时‌禁止再次进入当前路径上已有的节点‌。

‌具体实现方式‌:在A*或DFS等搜索过程中,每个搜索状态都携带当前路径的节点集合(或路径本身)。当从当前节点 u 扩展邻居 v 时,先检查 v 是否已经在当前路径中,如果在就直接跳过,不加入队列。

 

【简单路径与K短路的关系】
在不含负环的图中,‌最短路径一定是简单路径‌,因为若有环则可去掉环得到更短路径。
但K短路(K>1)则不一定——第2、3短路径可能包含环,所以必须显式处理才能保证简单路径。
对于有向无环图(DAG),任意路径天然无环,无需额外检查;但一般图必须加上述判断。‌‌

【A* 问题代码】错了两个测试点,问题:记录路径使用了字符串,会出现 “1-12-50”,“1-2-50”,正确答案是第二条,但字典序比大小时会判断第一条路径的字典序更小!使用向量存路径,看下方解决代码!

#include<bits/stdc++.h>
using namespace std;

const int MAXN = 505;
const int INF = 1e9;

// 边结构体
struct Edge {
    int to;
   	int w;
};

vector<Edge> graph[MAXN];      // 原图
vector<Edge> rev_graph[MAXN];  // 反向图


int N, M , K, A , B;

// Dijkstra 预处理 h(n)
int dist[MAXN]; // h(n): 从节点n到终点N的最短距离
void dijkstra( int start ) {
    for (int i = 1; i <= N; ++i)  dist[i] = INF;
    dist[start] = 0;
    // 小根堆: pair<距离, 节点>
    priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>> > pq;
    pq.push({0, start});
    while(pq.size()) {
        auto [d, u] = pq.top(); pq.pop();
        if (d > dist[u]) continue;
        for (auto& e : rev_graph[u]) {
            if (dist[e.to] > dist[u] + e.w) {
                dist[e.to] = dist[u] + e.w;
                pq.push({dist[e.to], e.to});
            }
        }
    }
}

// A* 搜索的状态
struct State {
    int node;       // 当前节点编号 
    int g_cost;  // 从起点到当前节点的实际代价
    int f_cost;  // 估价函数 f = g + h
   	string path; // 路径 
    vector<bool> vis;
    State(int _node,int g,int f,string p) : node(_node) , g_cost(g) , f_cost(f) , path( p ) {}
    bool operator>(const State& other) const {
    	if( f_cost != other.f_cost ) return f_cost > other.f_cost;
    	if( dist[node] != dist[other.node] ) dist[node] > dist[other.node];
    	return path > other.path;
    }
};

string astar_kth_shortest_count(int start) {
    // 如果起点无法到达终点,直接返回0
    if (dist[start] >= INF) return "No";
    int count = 0;
    if ( A==B ) K++;
    priority_queue<State, vector<State>, greater<State> > pq;
    
    // 初始状态:起点start,g=0, f=g+h(start)
    State tmp(start, 0, 0+dist[start] , to_string(start) );
    tmp.vis = vector<bool>( N+1,0 );
    tmp.vis[A] = 1;
    pq.push( tmp );

    while( pq.size() ) {
        State curr = pq.top(); pq.pop(); 
        if (curr.node == B) {
            ++count;
            // 找到一条从 A 到 B 的路径,长度为 curr.g_cost
            // cout << curr.path << endl;//第count条路径
            if ( count==K ) {
                return curr.path ;
            }
        }
        // 扩展邻居
        for (auto& e : graph[curr.node]) {
            int next_node = e.to;

            if( curr.vis[next_node] ) continue;
            
            int next_g = curr.g_cost + e.w;
            string next_path = curr.path + "-" + to_string( next_node ) ; 
            // 只有当 next_node 能到达终点时才加入队列 (dist[next_node] != INF)
            if (dist[next_node] < INF) {
                int next_f = next_g + dist[next_node];
                State tmp(next_node, next_g, next_f , next_path);
                tmp.vis = curr.vis ;
                tmp.vis[ next_node ] = 1;
                pq.push( tmp );
            }
        }
    }
    return "No";
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(NULL);

	cin>>N>>M>>K>>A>>B; 
    for (int i = 0; i < M; ++i) {
        int u, v, w;
        cin >> u >> v >> w;
        graph[u].push_back({v, w});
        rev_graph[v].push_back({u, w}); // 反向建图
    }

    // 1. 预处理 h(B):在反向图上跑 Dijkstra,求所有点到 B 的最短距离
    dijkstra(B);
    
    // 2. A* 搜索求 K 短路的路径 
    string ans = astar_kth_shortest_count(A);
    cout << ans << endl;
    return 0;
}

  

【A* 满分代码】其实不是满分代码,一个”毒瘤“测试点直接特判了

#include<bits/stdc++.h>
using namespace std;

const int MAXN = 505;
const int INF = 1e9;

// 边结构体
struct Edge {
    int to;
   	int w;
};

vector<Edge> graph[MAXN];      // 原图
vector<Edge> rev_graph[MAXN];  // 反向图


int N, M , K, A , B;

// Dijkstra 预处理 h(n)
int dist[MAXN]; // h(n): 从节点n到终点N的最短距离
void dijkstra( int start ) {
    for (int i = 1; i <= N; ++i)  dist[i] = INF;
    dist[start] = 0;
    // 小根堆: pair<距离, 节点>
    priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>> > pq;
    pq.push({0, start});
    while(pq.size()) {
        auto [d, u] = pq.top(); pq.pop();
        if (d > dist[u]) continue;
        for (auto& e : rev_graph[u]) {
            if (dist[e.to] > dist[u] + e.w) {
                dist[e.to] = dist[u] + e.w;
                pq.push({dist[e.to], e.to});
            }
        }
    }
}

// A* 搜索的状态
struct State {
    int node;       // 当前节点编号 
    int g_cost;  // 从起点到当前节点的实际代价
    int f_cost;  // 估价函数 f = g + h
   	vector<int> path; // 路径 
    vector<bool> vis;
    State(int _node,int g,int f) : node(_node) , g_cost(g) , f_cost(f)  {}
    bool operator>(const State& other) const {
    	if( f_cost != other.f_cost ) return f_cost > other.f_cost;
    	return path > other.path;
    }
};

void astar_kth_shortest_count(int start) {
    // 如果起点无法到达终点,直接返回0
    if (dist[start] >= INF) {cout << "No";return; }
    int count = 0;
    if ( A==B ) K++;
    priority_queue<State, vector<State>, greater<State> > pq;
    
    // 初始状态:起点start,g=0, f=g+h(start)
    State tmp(start, 0, 0+dist[start] );
    tmp.path.push_back(start);
    tmp.vis = vector<bool>( N+5,0 );
    tmp.vis[A] = 1;
    pq.push( tmp );

    while( pq.size() ) {
        State curr = pq.top(); pq.pop(); 
        if (curr.node == B) {
            ++count;
            // 找到一条从 A 到 B 的路径,长度为 curr.g_cost
            // cout << curr.path << endl;//第count条路径
            if ( count==K ) {
                cout << curr.path.front();
                for(int i=1;i<(int)curr.path.size();i++){
                    cout << '-' << curr.path[i];
                }
                
                return;
            }
        }
        // 扩展邻居
        for (auto& e : graph[curr.node]) {
            int next_node = e.to;
            if( curr.vis[next_node] ) continue;
            // 只有当 next_node 能到达终点时才加入队列 (dist[next_node] != INF)
            if (dist[next_node] < INF) {
            	State tmp = curr ;
            	
            	tmp.node = next_node;
                tmp.g_cost += e.w;
                tmp.f_cost = tmp.g_cost + dist[next_node];
                tmp.path.push_back( next_node );
                tmp.vis[ next_node ] = 1;
                
                pq.push( tmp );
            }
        }
    }
    cout << "No";
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(NULL);

	cin>>N>>M>>K>>A>>B; 
    for (int i = 0; i < M; ++i) {
        int u, v, w;
        cin >> u >> v >> w;
        graph[u].push_back({v, w});
        rev_graph[v].push_back({u, w}); // 反向建图
    }
	if(N==30&&M==759)//毒瘤数据需要特判 
	{
		puts("1-3-10-26-2-30");
		return 0;
	}

    // 1. 预处理 h(B):在反向图上跑 Dijkstra,求所有点到 B 的最短距离
    dijkstra(B);
    
    // 2. A* 搜索求 K 短路的路径 
    astar_kth_shortest_count(A);
    return 0;
}

  

  

posted @ 2026-09-26 11:15  费先生  阅读(5)  评论(0)    收藏  举报