18th ZJCPC D - Shortest Path Query

D - Shortest Path Query

题意:

给定一个图,图中每一条边的两点符合以下条件:两点编号二进制表示下一个位另一个的前缀。给出q次询问,每次给出u,v,询问u和v之间的最短路。

解析:

字典树符合一个点的为其所有子节点的前缀,故使用字典树对所有点建树,并将原图中的边附加上去,得到一颗有附加边的完全二叉树。对于该图中的任一节点,一个点无法不经过该点从左子树走到右子树,故对于询问的u,v,只需求u和v的所有公共祖先,并对所有公共祖先求 m i n ( d i s [ L c a ] [ u ] + d i s [ L c a ] [ v ] ) min(dis[Lca][u]+dis[Lca][v]) min(dis[Lca][u]+dis[Lca][v])即可。对每个公共祖先求最短路可以在用到的时候再求,可以提高速度。

代码:
#include <bits/stdc++.h>

using namespace std;
#define fast                          \
       ios_base::sync_with_stdio(false); \
       cin.tie(NULL);
const int N = 1e5 + 10;
const int M = 1e6 + 10;
const int INF = 0x3f3f3f3f;
const long long LINF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1e9 + 7;
const double eps = 1e-3;
int tree[M][2];
int idx = 0;
struct node{
    int u,v,next;
    long long w;
}a[M];
int f[N],cnt;
void add(int u,int v,int w){
    a[++cnt].v = v;
    a[cnt].w = w;
    a[cnt].next = f[u];
    f[u] = cnt;
}
int n, m;
int father[N][30], dep[N];
void build(int u, int fa, int deep){
    if(u > n)   return;
    father[u][0] = fa;
    dep[u] = deep;
    build(u << 1, u, deep + 1);
    build(u << 1 | 1, u, deep + 1);
}
int lg = 0;
int lca(int x, int y) {
    if(dep[x] < dep[y]) swap(x, y);
    for (int i = lg; i >= 0; i--) {
        if(dep[father[x][i]] >= dep[y]) x = father[x][i];
    }
    if(x == y)  return x;
    for (int i = lg; i >= 0; i--) {
        if(father[x][i] != father[y][i]){
            x = father[x][i];
            y = father[y][i];
        }
    }
    return father[x][0];
}
unordered_map<int, unordered_map<int, long long>> dis;
unordered_map<int, unordered_map<int, bool>> vis;
void dijkstra(int s) {
    priority_queue<pair<long long, int>> q;
    q.push({0, s});
    dis[s][s] = 0;
    while (!q.empty()) {
        int u = (q.top()).second;
        q.pop();
        if(vis[s][u])   continue;
        vis[s][u] = 1;
        for (int i = f[u]; i; i = a[i].next) {
            int v = a[i].v;
            if (dep[v] <= dep[s]) continue;
            long long w = a[i].w;
            if (dis[s].count(v) == 0 || dis[s][v] > dis[s][u] + w) {
                dis[s][v] = dis[s][u] + w;
                q.push({-dis[s][v], v});
            }
        }
    }
}
int main(){
    cin >> n >> m;
    build(1, 0, 1);
    lg = (int)log(n) / log(2) + 1;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= lg; j++) {
            father[i][j] = father[father[i][j - 1]][j - 1];
        }
    }
    int u, v, w;
    for (int i = 1; i <= m; i++){
        scanf("%d%d%d", &u, &v, &w);
        add(v, u, w);
        add(u, v, w);
    }
    int q;
    cin >> q;
    while(q--) {
        int u, v;
        scanf("%d%d", &u, &v);
        int Lca = lca(u, v);
        long long Min = LINF;
        while(Lca) {
            if(dis[Lca].count(Lca) == 0) dijkstra(Lca);
            if(dis[Lca].count(u) && dis[Lca].count(v)) Min = min(Min, dis[Lca][u] + dis[Lca][v]);
            Lca = father[Lca][0];
        }
        if(Min == LINF)  printf("-1\n");
        else    printf("%lld\n", Min);
    }
    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值