bzoj 5415 [Noi2018]归程 可持久化并查集/kruskal重构树

题面

题目传送门

解法

先看一看部分分

1-6

直接用dijkstra求最短路,然后判断一下即可

时间复杂度:\(O((n+m)\ log\ n)\)

期望得分:\(30\)

7-11

倍增求出路径上的最小值,然后比较一下即可

时间复杂度:\(O(q\ log\ n)\)

期望得分:\(30+25=55\)

12-14

发现可以离线

将所有边按照海拔从大到小排序,询问按照海拔从大到小排序

那么就可以转化成不断加边,然后求\(v\)所在连通块中到1距离最短的是多少

直接并查集即可

时间复杂度:\(O(q\alpha(n))\)

期望得分:\(30+25+15=70\)

15-16

直接暴力即可

时间复杂度:\(O(nq)\)

期望得分:\(30+25+15+10=80\)

代码就不给了,太长了

100分算法

根据离线的那一部分,我们发现,因为每一次的起始点是不固定的,且强制在线,所以不方便维护

直接可持久化并查集!!!

时间复杂度:\(O(q\ log^2\ n)\)

md代码复杂度巨大,时间好不容易才苟过去

代码(可持久化并查集)

#include <bits/stdc++.h>
#define PI pair <int, int>
#define mp make_pair
#define N 200010
using namespace std;
template <typename node> void read(node &x) {
    x = 0; int f = 1; char c = getchar();
    while (!isdigit(c)) {if (c == '-') f = -1; c = getchar();}
    while (isdigit(c)) x = x * 10 + c - '0', c = getchar(); x *= f;
}
struct Node {
    int lc, rc, fa, mn, siz;
} t[N * 60];
struct Edge {
    int x, y, v, p;
    bool operator < (const Edge &a) const {
        return p > a.p;
    }
} a[N * 2];
struct Info {
    int next, num, v, p;
} e[N * 6];
struct Ret {
    int first, second, third;
};
int n, m, cnt, tot, used[N], dis[N], rt[N * 2], b[N * 2];
void add(int x, int y, int v, int p) {
    e[++cnt] = (Info) {e[x].next, y, v, p};
    e[x].next = cnt;
}
void dijkstra(int s) {
    for (int i = 1; i <= n; i++)
        dis[i] = 1 << 30, used[i] = 0;
    priority_queue <PI, vector <PI>, greater <PI> > h;
    dis[s] = 0; h.push(mp(0, s));
    while (!h.empty()) {
        PI tmp = h.top(); h.pop();
        int x = tmp.second;
        if (used[x]) continue; used[x] = 1;
        for (int p = e[x].next; p; p = e[p].next) {
            int k = e[p].num, v = e[p].v;
            if (dis[k] > dis[x] + v) {
                dis[k] = dis[x] + v;
                h.push(mp(dis[k], k));
            }
        }
    }
}
void build(int &k, int l, int r) {
    k = ++tot;
    if (l == r) {
        t[k].fa = l, t[k].mn = dis[l], t[k].siz = 1;
        return;
    }
    int mid = (l + r) >> 1;
    build(t[k].lc, l, mid), build(t[k].rc, mid + 1, r);
}
void ins(int &k, int cur, int l, int r, int x, int fa, int mn, int siz) {
    k = ++tot; t[k] = t[cur];
    if (l == r) {t[k].fa = fa, t[k].mn = mn, t[k].siz = siz; return;}
    int mid = (l + r) >> 1;
    if (x <= mid) ins(t[k].lc, t[cur].lc, l, mid, x, fa, mn, siz);
        else ins(t[k].rc, t[cur].rc, mid + 1, r, x, fa, mn, siz);
}
Ret query(int k, int l, int r, int x) {
    if (l == r) return (Ret) {t[k].fa, t[k].mn, t[k].siz};
    int mid = (l + r) >> 1;
    if (x <= mid) return query(t[k].lc, l, mid, x);
    return query(t[k].rc, mid + 1, r, x);
}
int Find(int now, int x) {
    int fa = x;
    while (true) {
        Ret tx = query(rt[now], 1, n, fa);
        if (tx.first == fa) return fa;
        fa = tx.first;
    }
}
void merge(int t, int x, int y) {
    int tx = Find(t, x), ty = Find(t, y);
    if (tx == ty) return;
    Ret a = query(rt[t], 1, n, tx), b = query(rt[t], 1, n, ty);
    int sx = a.third, sy = b.third;
    if (sx > sy) swap(tx, ty), swap(sx, sy);
    ins(rt[t], rt[t], 1, n, tx, ty, a.second, sx);
    ins(rt[t], rt[t], 1, n, ty, ty, min(a.second, b.second), sx + sy);
}
int calc(int n, int v) {
    int l = 1, r = n, ans = 0;
    while (l <= r) {
        int mid = (l + r) >> 1;
        if (b[mid] > v) ans = mid, l = mid + 1;
            else r = mid - 1;
    }
    return ans;
}
int main() {
    int T; read(T);
    while (T--) {
        read(n), read(m); tot = 0; cnt = n;
        for (int i = 1; i <= n; i++) e[i].next = 0;
        for (int i = 1; i <= m; i++) rt[i] = 0;
        for (int i = 1; i <= m; i++) {
            int x, y, v, p;
            read(x), read(y), read(v), read(p);
            a[i] = (Edge) {x, y, v, p}; b[i] = p;
            add(x, y, v, p), add(y, x, v, p);
        }
        dijkstra(1);
        sort(a + 1, a + m + 1);
        sort(b + 1, b + m + 1);
        int l = unique(b + 1, b + m + 1) - b - 1;
        reverse(b + 1, b + l + 1);
        build(rt[0], 1, n);
        for (int i = 1, j = 1; i <= l; i++) {
            rt[i] = rt[i - 1];
            for (; a[j].p == b[i]; j++) {
                int x = a[j].x, y = a[j].y;
                merge(i, x, y);
            }
        }
        int q, k, s, ans = 0;
        read(q), read(k), read(s);
        while (q--) {
            int x, p; read(x), read(p);
            x = (x + k * ans - 1) % n + 1, p = (p + k * ans) % (s + 1);
            int t = calc(l, p), tx = Find(t, x);
            Ret tmp = query(rt[t], 1, n, tx); ans = tmp.second;
            cout << ans << "\n";
        }
        memset(t, 0, (tot + 1) * 2);
    }
    return 0;
}

100分(kruskal重构树)

这是个什么东西呢??

就是kruskal算法中合并两个连通块的时候,建一个虚拟节点,权值为边权,然后两个儿子为两个连通块的根

这样我们就可以每一次直接在这棵树上倍增,求子树最小值了

时间复杂度:\(O(q\ log\ n)\)

代码

#include <bits/stdc++.h>
#define int long long
#define PI pair <int, int>
#define mp make_pair
#define N 400010
using namespace std;
template <typename node> void read(node &x) {
    x = 0; int f = 1; char c = getchar();
    while (!isdigit(c)) {if (c == '-') f = -1; c = getchar();}
    while (isdigit(c)) x = x * 10 + c - '0', c = getchar(); x *= f;
}
struct Node {
    int x, y, v, p;
    bool operator < (const Node &a) const {
        return p > a.p;
    }
} a[N];
struct Edge {
    int next, num, v;
} e[N * 3];
int n, m, cnt, dis[N], used[N], v[N], p[N], f[N][21];
vector <int> E[N];
void add(int x, int y, int v) {
    e[++cnt] = (Edge) {e[x].next, y, v};
    e[x].next = cnt;
}
void dijkstra(int s) {
    for (int i = 1; i <= 2 * n; i++)
        dis[i] = LONG_LONG_MAX, used[i] = 0;
    priority_queue <PI, vector <PI>, greater <PI> > h;
    dis[s] = 0; h.push(mp(0, s));
    while (!h.empty()) {
        PI tmp = h.top(); h.pop();
        int x = tmp.second;
        if (used[x]) continue; used[x] = 1;
        for (int p = e[x].next; p; p = e[p].next) {
            int k = e[p].num, v = e[p].v;
            if (dis[k] > dis[x] + v)
                dis[k] = dis[x] + v, h.push(mp(dis[k], k));
        }
    }
}
int Find(int x) {
    if (p[x] == x) return x;
    return p[x] = Find(p[x]);
}
int query(int x, int p) {
    for (int i = 20; i >= 0; i--)
        if (f[x][i] && v[f[x][i]] > p) x = f[x][i];
    return dis[x];
}
main() {
    int T; read(T);
    while (T--) {
        read(n), read(m); cnt = n;
        for (int i = 1; i <= n; i++) e[i].next = 0;
        for (int i = 1; i <= m; i++) {
            int x, y, v, p;
            read(x), read(y), read(v), read(p);
            add(x, y, v), add(y, x, v);
            a[i] = (Node) {x, y, v, p};
        }
        sort(a + 1, a + m + 1);
        dijkstra(1); int tot = n;
        memset(f, 0, sizeof(f));
        for (int i = 1; i <= 2 * n; i++)
            p[i] = i, v[i] = -1, E[i].clear();
        for (int i = 1; i <= m; i++) {
            int tx = Find(a[i].x), ty = Find(a[i].y), tp = a[i].p;
            if (tx != ty) {
                p[tx] = p[ty] = ++tot, v[tot] = tp;
                E[tot].push_back(tx), E[tot].push_back(ty);
                dis[tot] = min(dis[tx], dis[ty]);
                f[tx][0] = tot, f[ty][0] = tot;
            }
        }
        for (int j = 1; j <= 20; j++)
            for (int i = 1; i <= tot; i++)
                if (f[i][j - 1]) f[i][j] = f[f[i][j - 1]][j - 1];
        int q, k, s, ans = 0;
        read(q), read(k), read(s);
        while (q--) {
            int x, p; read(x), read(p);
            x = (x + k * ans - 1) % n + 1, p = (p + k * ans) % (s + 1);
            ans = query(x, p); cout << ans << "\n";
        }
    }
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/copperoxide/p/9478378.html