【NOI 2018】归程(Kruskal重构树 + 倍增)

本文介绍了一道名为“归程”的NOI2018竞赛题目的解题思路。通过使用Kruskal重构树进行预处理,并结合倍增技巧,实现了高效的在线查询解答。文中提供了完整的C++代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接:【NOI 2018】归程

题目大意:给定一个加权无向图, q q 次询问,每次询问从 v 点只经过海拔 >w > w 的边到达的点中到 1 1 号点的最短路为多少。强制在线。

首先,预处理出 1 号点到所有点的最短路。然后,每个询问就可以变成:从 v v 点只经过 >w 的边到达的点 u u dis[u] 的最小值。

这个可以用 Kruskal K r u s k a l 重构树来做。没学过的同学可以参考 这篇 Blog
作出 Kruskal K r u s k a l 重构树后,我们处理出点 u u 2k 级父亲和他子树中到点 1 1 的最小值。对于每个询问,我们倍增求出点 v 能爬到的最高点,在输出该点的子树最小值即可。

#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
#include <iostream>
using namespace std;
const int logn = 20;
const int maxn = 200005;
const int maxm = 400005;
const int maxe = 800005; 
int T, n, m, q, k, s, dist[maxn], fa[maxm], wei[maxm], f[maxm];
int tot, ter[maxe], len[maxe], nxt[maxe], lnk[maxm], p[maxm][logn];
struct edge {
    int u, v, w;
    edge() {}
    edge(int nu, int nv, int nw) { u = nu, v = nv, w = nw; }
    bool operator<(edge x) const{ return w > x.w; }
} e[maxm];
void addedge(int u, int v, int w) {
    ter[++tot] = v, len[tot] = w;
    nxt[tot] = lnk[u], lnk[u] = tot;
}
int find(int x) {
    return fa[x] ? fa[x] = find(fa[x]) : x;
}
void dijkstra() {
    priority_queue<edge> pq; pq.push(edge(1, 0, 0));
    memset(dist, 0x3f, sizeof(dist)); dist[1] = 0;
    while (!pq.empty()) {
        edge x = pq.top(); pq.pop();
        for (int i = lnk[x.u]; i; i = nxt[i]) {
            if (dist[ter[i]] > dist[x.u] + len[i]) {
                dist[ter[i]] = dist[x.u] + len[i];
                pq.push(edge(ter[i], 0, dist[ter[i]]));
            }
        }
    }
}
int kruskal() {
    sort(e + 1, e + m + 1);
    memset(fa, 0, sizeof(fa));
    int u, v, idx = n;
    for (int i = 1; i <= m; i++) {
        u = find(e[i].u), v = find(e[i].v);
        if (u == v) continue;
        fa[u] = fa[v] = ++idx, wei[idx] = e[i].w;
        addedge(idx, u, 0), addedge(idx, v, 0);
    }
    return idx;
}
void dfs(int u, int l) {
    for (int i = 0; (p[u][i + 1] = p[p[u][i]][i]); i++);
    for (int v, i = lnk[u]; i; i = nxt[i]) {
        if ((v = ter[i]) == l) continue;
        p[v][0] = u, dfs(v, u), f[u] = min(f[u], f[v]);
    }
    if (u <= n) f[u] = dist[u];
}
int main() {
    for (scanf("%d", &T); T--; ) {
        scanf("%d %d", &n, &m);
        int u, v, l, a;
        tot = 0, memset(lnk, 0, sizeof(lnk));
        for (int i = 1; i <= m; i++) {
            scanf("%d %d %d %d", &u, &v, &l, &a);
            addedge(u, v, l), addedge(v, u, l), e[i] = edge(u, v, a);
        }
        dijkstra();
        tot = 0, memset(lnk, 0, sizeof(lnk));
        memset(f, 0x3f, sizeof(f)), memset(p, 0, sizeof(p));
        dfs(kruskal(), 0);
        int w, lans = 0;
        for (scanf("%d %d %d", &q, &k, &s); q--; ) {
            scanf("%d %d", &v, &w);
            v = (v + k * lans - 1) % n + 1, w = (w + k * lans) % (s + 1);
            for (int i = 18; ~i; i--)  if (wei[p[v][i]] > w)   v = p[v][i];
            printf("%d\n", lans = f[v]);
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值