【ACM】- PAT. 1018 Public Bike Management 【图 - 最短路径】

23 篇文章 0 订阅
题目链接
题目分析

给出结点信息,输出最短路径;
总站编号为0,其他站点编号为1-N

多条最短路径时,其他标尺:
在最短路径过程中,必须把每个结点的权值调整到题目要求的最佳;

  • 标尺一:选择需要从总站带出最少量的路径
  • 标尺二:仍有多条,则选择需要带回最少量的路径
  • 【陷阱】:不能在返程过程中才调整结点,去的时候就需要调整好数量,否则两个测试点错误(25`)
解题思路

Dijkstra()算法 查找所有最短路径并保存,之后用DFS()筛选;

筛选策略:由于不满足最优子结构,回溯到起点,只能正向枚举完整路径(vector<int>),逐个结点计算需求量和带回量!

测试数据

10 4 4 5
4 8 9 0
0 1 1
1 2 1
1 3 2
2 3 1
3 4 1

输出: 1 0->1->2->3->4 2


AC程序(C++)
/**********************************
*@ID: 3stone
*@ACM: PAT.A1018 Emergency
*@Time: 18/8/20
*@IDE: VSCode 2018 + clang++
***********************************/
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
#include<cmath>

using namespace std;

const int maxn = 505;
const int INF = 0x7fffffff;

int G[maxn][maxn]; //图
int d[maxn];  //起点到此点的距离
vector<int> pre[maxn]; //记录前驱结点
int vis[maxn]; //标记是否访问
int weight[maxn]; //点的权重
int N, M, capacity, Ed, perfect_num; //结点数、边数、最大容量、

void Dijkstra(int s) {
    //总站点 初始化 weight[s]不需要初始化了,因为用不到
    d[s] = 0;

    for(int i = 0; i < N; i++) {

        int u = -1, MIN = INF;
        for(int j = 0; j <= N; j++) {
            if(vis[j] == false && d[j] < MIN){
                u = j;
                MIN = d[j];
            }
        }
        if(u == -1) return;
        vis[u] = true;
        for(int v = 0; v <= N; v++) {
            if(vis[v] == false && G[u][v] != INF){
                if(d[u] + G[u][v] < d[v]) {
                    d[v] = d[u] + G[u][v];
                    pre[v].clear();
                    pre[v].push_back(u);
                } else if (d[u] + G[u][v] == d[v]) {
                    pre[v].push_back(u);  //权重更小,换路
                }
            }//if
        }//for - v

    }//for - i

}//Dijkstra


int min_needed, min_remain;  //需求量,带回量
vector<int> best_route; //最佳最短路径
void dfs_route(int ed, vector<int> cur_route) {
    if(ed == 0) { //回溯到总站,cur_route保存了一条最短路径
        /* 由于不满足最优子结构,要计算需要带出多少,只能到达起点,正向枚举最短路径每个结点
        */
        //cur_route.push_back(ed); //起点不需要加入路径不是吗?
        int needed = 0, remain = 0;

        //正向枚举路径
        for(int i = cur_route.size() - 1; i >= 0; i--){
            int id = cur_route[i];
            if(weight[id] > 0) {
                remain += weight[id];
            } else if(weight[id] < 0) {
                if(remain >= abs(weight[id])) {
                    remain += weight[id];
                } else {
                    needed += (abs(weight[id]) - remain);
                    remain = 0;
                }
            }
        }

        if(needed < min_needed) {
            min_needed = needed;
            min_remain = remain;
            best_route = cur_route;
        } else if(needed == min_needed && remain < min_remain) {
            min_remain = remain;
            best_route = cur_route;
        }
        return;

    }

    //枚举每条最短路径点
    cur_route.push_back(ed);
    for(int i = 0; i < pre[ed].size(); i++) {
        dfs_route(pre[ed][i], cur_route);
    }
    cur_route.pop_back();
}


int main() {
    int c1, c2, L;
    while(scanf("%d%d%d%d", &capacity, &N, &Ed, &M) != EOF) {

        fill(d, d + maxn, INF); //最短距离
        fill(vis, vis + maxn, false); //标记访问
        fill(G[0], G[0] + maxn * maxn, INF); //边权

        perfect_num = capacity / 2;
        //输入结点权重(当前车辆数)
        for(int i = 1; i <= N; i++) {
            scanf("%d", &weight[i]);
            weight[i] -= perfect_num;  //把点权更新为该站点车辆的需求量,正为溢出,负为缺少
        }
        //输入边信息
        for(int i = 0; i < M; i++) {
            scanf("%d%d%d", &c1, &c2, &L);
            G[c1][c2] = L; //无向图转为 双向边
            G[c2][c1] = L;
        }


        Dijkstra(0);

        min_needed = INF;
        min_remain = INF;
        best_route.clear();
        vector<int> cur_route;

        dfs_route(Ed, cur_route);

        printf("%d 0->", min_needed);  //需求量

        for(int i = best_route.size() - 1; i > 0; i--) {
            printf("%d->", best_route[i]);
        }
        printf("%d", best_route[0]);

        printf(" %d\n", min_remain); //带回量
    }

    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值