久未放晴的天|

TulipeNoire

园龄:1年10个月粉丝:18关注:17

「SDOI2019」世界地图

询问时假设我们已经知道了左右两边的点的最小生成树长什么样,我们就可以将左右两边合并,再考虑横跨边的贡献。我们直接把两棵树的边拿来跑新的最小生成树。但是复杂度肯定难以承受。

回忆一下,我们有一种往最小生成树上加边的算法:就是找到树上这两点路径上的最长边,看看删掉它会不会更优。我们合并时只会涉及到原树的 O(n) 个点。这启示我们只维护前/后缀最小生成树的虚树,边权是原树对应路径边权最大值。使用 Kruskal 算法合并时,一开始记录原树所有边的边权和 s,然后我们把边按照新边权从小到大排序,考虑如果已经连通,就让 s 减去这条边的新边权。最后的 s 就是新最小生成树边权和。然后我们就可以把加入时两端点已经连通的边忽略掉,在新树上保留关键点了。我们需要保留的关键点是最左边 n 个和最右边 n 个。容易发现这样可以 O(nlogn) 把一个前/后缀拓展,或者进行一次查询。

实现的时候只需要用 std::vector 记下虚树上的边,以及前/后缀最小生成树边权和。合并时先跑 Kruskal,暂时存下新树,并使用 dfs 在 O(n) 的时间复杂度内得到新树在保留新关键点集的虚树。有点难实现,还有点卡常,所以应当尽量消去时间复杂度里面的 log 因子。

代码很丑,不建议模仿代码的实现,可能作为理解算法的参考比较好(虽然好像写得难以理解)。

#include <bits/stdc++.h>
#define fi first
#define se second
using namespace std;
using LL = long long;
using uint = unsigned;
using pii = pair<int, int>;
uint SA, SB, SC;
int lim;
inline uint read() {
    uint f = 0;
    char c = getchar();
    while (!isdigit(c)) c = getchar();
    while (isdigit(c)) f = f * 10 + (c - '0'), c = getchar();
    return f;
}
inline int getweight() {
    SA ^= SA << 16;
    SA ^= SA >> 5;
    SA ^= SA << 1;
    uint t = SA;
    SA = SB;
    SB = SC;
    SC ^= t ^ SA;
    return SC % lim + 1;
}
const int N = 105, M = 10005;
int n, m, q, R[N][M], C[N][M];
struct DSU {
    int fa[N * M];
    inline void reset(int x) {
        fa[x] = x;
    }
    int Find(int x) {
        if (fa[x] == x) return x;
        return fa[x] = Find(fa[x]);
    }
    inline void Merge(int x, int y) {
        fa[Find(x)] = Find(y);
    }
} D;
inline int id(int x, int y) {
    return (x - 1) * m + y;
}
struct Edge {
    int u, v, w;
    inline bool operator<(const Edge o) {
        return w < o.w;
    }
};
LL f[M], g[M];
vector<Edge> pre[M], suf[M];
vector<pii> G[N * M];
int fa[N * M], fav[N * M], sz[N * M];
bool vis[N * M];
void dfs(int p, int lst) {
    fa[p] = lst;
    if (sz[p]) vis[p] = 1;
    for (auto x : G[p]) {
        if (x.fi != lst) {
            fav[x.fi] = x.se, dfs(x.fi, p);
            if (sz[p] && sz[x.fi]) vis[p] = 1;
            sz[p] += sz[x.fi];
        }
    }
}
inline Edge FindEdge(int x) {
    int u = x, z = fav[x];
    x = fa[x];
    while (!vis[x]) z = max(z, fav[x]), x = fa[x];
    return {u, x, z};
}
inline pair<vector<Edge>, LL> MergeTwo(vector<Edge> &x, vector<Edge> &y, vector<int> &mark, LL sum) {
    vector<Edge> vec, ret;
    vector<int> nodes;
    for (auto o : x) vec.push_back(o);
    for (auto o : y) vec.push_back(o), sum += o.w;
    for (auto o : vec) nodes.push_back(o.u), nodes.push_back(o.v);
    sort(nodes.begin(), nodes.end());
    nodes.erase(unique(nodes.begin(), nodes.end()), nodes.end());
    for (auto o : nodes) D.reset(o);
    sort(vec.begin(), vec.end());
    for (auto o : vec) {
        if (D.Find(o.u) == D.Find(o.v)) sum -= o.w;
        else D.Merge(o.u, o.v), G[o.u].push_back({o.v, o.w}), G[o.v].push_back({o.u, o.w});
    }
    int rt = mark[0];
    for (auto o : mark) sz[o] = 1;
    dfs(rt, 0);
    for (auto o : nodes) if (vis[o] && o != rt) ret.push_back(FindEdge(o));
    for (auto o : nodes) sz[o] = 0, vis[o] = 0, vector<pii>().swap(G[o]);
    return {ret, sum};
}
inline LL MergeThree(vector<Edge> &x, vector<Edge> &y, vector<Edge> &z, LL sum) {
    vector<Edge> vec;
    for (auto o : x) vec.push_back(o);
    for (auto o : y) vec.push_back(o);
    for (auto o : z) vec.push_back(o), sum += o.w;
    for (auto o : vec) D.reset(o.u), D.reset(o.v);
    sort(vec.begin(), vec.end());
    for (auto o : vec) {
        if (D.Find(o.u) == D.Find(o.v)) sum -= o.w;
        else D.Merge(o.u, o.v);
    }
    return sum;
}
inline LL solve(int l, int r) {
    vector<Edge> tmp;
    for (int i = 1; i <= n; i++) tmp.push_back({id(i, 1), id(i, m), R[i][m]});
    return MergeThree(pre[l - 1], suf[r + 1], tmp, f[l - 1] + g[r + 1]);
}
int main() {
    freopen("in", "r", stdin);
    freopen("out", "w", stdout);
    n = read(), m = read(), SA = read(), SB = read(), SC = read(), lim = read();
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            R[i][j] = getweight();
        }
    }
    for (int i = 1; i < n; i++) {
        for (int j = 1; j <= m; j++) {
            C[i][j] = getweight();
        }
    }
    for (int i = 1; i < n; i++) pre[1].push_back({id(i, 1), id(i + 1, 1), C[i][1]}), f[1] += C[i][1];
    for (int i = 2; i <= m; i++) {
        vector<Edge> tmp;
        vector<int> mark;
        for (int j = 1; j <= n; j++) {
            mark.push_back(id(j, 1)), mark.push_back({id(j, i)});
            tmp.push_back({id(j, i - 1), id(j, i), R[j][i - 1]});
            if (j < n) tmp.push_back({id(j, i), id(j + 1, i), C[j][i]});
        }
        auto e = MergeTwo(pre[i - 1], tmp, mark, f[i - 1]);
        pre[i] = e.fi, f[i] = e.se;
    }
    for (int i = 1; i < n; i++) suf[m].push_back({id(i, m), id(i + 1, m), C[i][m]}), g[m] += C[i][m];
    for (int i = m - 1; i; i--) {
        vector<Edge> tmp;
        vector<int> mark;
        for (int j = 1; j <= n; j++) {
            mark.push_back(id(j, m)), mark.push_back({id(j, i)});
            tmp.push_back({id(j, i + 1), id(j, i), R[j][i]});
            if (j < n) tmp.push_back({id(j, i), id(j + 1, i), C[j][i]});
        }
        auto e = MergeTwo(suf[i + 1], tmp, mark, g[i + 1]);
        suf[i] = e.fi, g[i] = e.se;
    }
    q = read();
    while (q--) {
        int l = read(), r = read();
        printf("%lld\n", solve(l, r));
    }
    return 0;
}

本文作者:TulipeNoire

本文链接:https://www.cnblogs.com/TulipeNoire/p/18730251/P5360

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   TulipeNoire  阅读(10)  评论(1编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起