2017年湖南省第十三届大学生程序设计大赛 I 题 “Nearest Maintenance Point” CSG - 1109(最短路,dijkstra)

题目链接


一.题目内容

给一个双向图,点为城市,边为路,有几个特殊的城市设有维修点,问离某个点最近的维修点有哪些

样例解释

在这里插入图片描述

在这里插入图片描述
离3最近的就是3,离2最近的是1,离4最近的有1和3


二.解题思路

题目所给数据太大,若求所有维修点的单源最短路则要最多1000次dijkstra,所以只能把所有维修点放入堆中跑一次最短路,这时dis数组记录不是从某点到某点的最短路长度,而是某个点离最近维修点的最短路长度。

那么问题来了,如果把所有维修点都一起跑最短路,那么怎么判断该最短路是来源于哪个维修点呢?

这时就有个骚操作,用bitset来存储当前点的最短路是来源于哪个维修点。所给数据中维修点最多1000次,所以创一个1000大小的bitset,每一位代表着每一个维修点,值若为1则代表当前点的最短路来源于该维修点。

比如离维修点最近的当然就是它本身,所以维修点的bitset中它自身所对应的bitset中的位置就是1。

在跑dijkstra时,若从 u 到达 v,
①所得新路径长度<dis[v]时,则更新dis[v],并将 v 所对应的bitset设为u所对应得bitset,因为v当前的最短路径来源于u,而u的最短路径来源于它的bitset里面值为1的维修点,所以v的最短路径现在来源于u的bitset里面值为1的维修点
② 所得新路径长度==dis[v]时,则将 v 的bitset与 u 的bitset用 '| '操作合并,道理同上,只是 v 的最短路同时也来源于 v 本身的bitset中值为1的维修点。

最后只需循环一遍所求的点的bitset,值为1的就代表是最近的维修点


三.解题代码

#include <bits/stdc++.h>
using namespace std;
#define rep(i, l, r) for (int i = l; i <= r; ++i)
#define rrep(i, l, r) for (int i = l; i >= r; --i)
#define int long long
int dir[4][2] = {{-1, 0}, {0, -1}, {1, 0}, {0, 1}};
const int N = 1e4 + 10, M = 1e9 + 7;
//-----------//
bitset<N>msk[N]; // 每个点对应一个bitset
int dis[N],ss[N];
struct node {
    int v,w;
    friend bool operator < (const node&t1,const node&t2) {
        return t1.w>t2.w;
    }
};
int n,m,s,q;
vector<node>a[N];
void dj() {
    priority_queue<node>q;
    rep(i,1,n) {
        msk[i].reset();
        dis[i]=LLONG_MAX;
    }
    rep(i,1,s) {
        q.push(node{ss[i],0}); //将所有维修点放入堆中
        dis[ss[i]]=0; //维修点最短路径为0
        msk[ss[i]][i]=1; //每个维修点最开始的bitset中值为1的就是它们本身
    }
    while(q.size()) {
        node cur=q.top();
        q.pop();
        if(dis[cur.v]<cur.w) continue;
        for(auto i:a[cur.v]) {
            int d=cur.w+i.w;
            if(dis[i.v]>d) {
                dis[i.v]=d;
                msk[i.v]=msk[cur.v]; //有新最短路径时将i.v的bitset用cur.v覆盖
                q.push(node{i.v,d});
            } else if(dis[i.v]==d) msk[i.v]|=msk[cur.v]; // 路径相同时,则合并
        }
    }
}
signed main() {
    while(~scanf("%lld%lld%lld%lld",&n,&m,&s,&q)) {
        rep(i,1,n) a[i].clear();
        while(m--) {
            int x,y,z;
            scanf("%lld%lld%lld",&x,&y,&z);
            a[x].push_back(node{y,z});
            a[y].push_back(node{x,z});
        }
        rep(i,1,s) scanf("%lld",&ss[i]);
        sort(ss+1,ss+s+1); // 先排序,因为最后输出要按从从小大大输出
        dj();
        while(q--) {
            int x;
            scanf("%lld",&x);
            vector<int>ans;
            rep(i,1,s) {
                if(msk[x][i]) ans.push_back(ss[i]); //若为1,则将维修点的点号放入答案数组
            }
            for(auto i:ans) printf("%lld ",i);
            puts("");
        }
    }
}






四.小结

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值