【线段树】线段树优化建图(CF786B题解)

在开始之前,你应该了解如下知识:

完全二叉树的性质

线段树的原理及使用

图的存储方式及图论基础

我们首先看一道题:洛谷链接/CodeForces链接/Vjudge链接

        上面这道题,我们首先考虑暴力建边,无论前面的建图部分还是后面的最短路,显然都是会超时的,然后我们观察题目,题目中提到每次对一整个[i, j]区间执行同一操作,所以我们考虑使用一些数据结构进行优化。

线段树优化建图

        我们考虑,如果建立一个线段树,再用线段树上的节点代表区间,既然同一个操作对于区间的每条边权值都相同,那么不就可以用图上单点向线段树上节点的一条边来表示一个点与一个区间之间的 j - i + 1 条边了吗?

        所以我们考虑这样的数据结构:

        用图中蓝色的边表示 2号点 连向区间 [1, 4] 的边。

        想法不错,可惜构不成完整的图,我们也许可以这样表示边的信息,但任何图论算法在这样的存储模式下都无法得到良好的发挥。主要矛盾在于,无法通过区间详细地表示点的编号,怎样让区间联系起来呢?很简单,让区间与单点建立联系就好了!单点是属于区间的,所以区间一定能够到达单点

        我们在线段树的节点之间建立红色的边,每条边的边权为零, 这样就可以表示具体的区间与其中包含的单点之间的联系了,如果某点能够到达这个区间,那么它就可以直接到达区间内的每一个点了!

        那么,如何让区间向单点建边呢?再来一个线段树处理这一部分就好啦~

        建立另一棵线段树,专门处理区间向单点建边的操作,这样的线段树加上新加的蓝边就表示了某一区间内的所有单点都可以通过这一条蓝边到达某个图上的节点

        最后我们添加一些边,将两棵线段树连接起来,通过线段树构建的图就完成了,我们最后得到的图就是这两棵线段树已经中间的边,我们的图论算法也就是要在这两棵线段树上进行的了,只不过最后要产生答案的只有中间的线性表中的那些单点。

        最后我们就得到了这样的结果,我们称上面这棵红边由根指向叶子的线段树叫做出树;下面这棵红边由叶子指向根的线段树为入树。出树用来处理边指向区间的情况,入树用来处理边由区间出发的情况(所以我总觉得它们的名字应该反过来)。

        单独为这些节点指定编号就可以在这张图上愉快地跑算法辣~(编号的指定顺序可以使用一些技巧,方便维护)。

         这道题中我没有建立中间的那个数组来存点,而是将两棵线段树直接相连,这样当然也是可行的,只不过时间复杂度会略高于上面的方法(因为还需要O(logn)查找单点)。

#include<bits/stdc++.h>
using namespace std;
//Legacy
#define int long long
const int maxn = (int)2e5 + 10;
struct node {
    int l, r;
    int ls, rs;
} tr[maxn << 2];
struct edge {
    int to = 0;
    int vl = 0;
};
vector<edge> ed[maxn << 2];
queue<int> qwq;
struct staCk {
    int kkksc03[maxn];
    int tOp;
    void push(int x) {
        kkksc03[++tOp] = x;
        return;
    }
    void clear() {
        tOp = 0;
        return;
    }
    void makeEdge(int id, int w) {
        edge dxy;   dxy.vl = w;
        for (int i = 1; i <= tOp; i++) {
            dxy.to = kkksc03[i];
            ed[id].push_back(dxy);
        }
        return;
    }
} stk;
struct pqnode {
    int node;
    int ds;
    bool operator< (const pqnode& yxc) const {
        return ds > yxc.ds;
    }
};
priority_queue<pqnode> pq;
bool cnnct[maxn << 2];
int dis[maxn << 2];
int pointStart;
int nmaxin1;
int n, q, s;

void build(int id, int l, int r, bool in, int u) {
    if (!in) nmaxin1 = max(id, nmaxin1);
    tr[id].l = l;
    tr[id].r = r;
    if (l == r) {
        if (in) {
            edge zhx;   zhx.to = id;
            ed[id - nmaxin1].push_back(zhx);
        } else if (l == u) pointStart = id;
        return;
    }
    int mid = l + r >> 1;
    tr[id].ls = in ? (id - nmaxin1 << 1) + nmaxin1 : id << 1;
    tr[id].rs = in ? (id - nmaxin1 << 1 | 1) + nmaxin1 : id << 1 | 1;
    build(tr[id].ls, l, mid, in, u);
    build(tr[id].rs, mid + 1, r, in, u);
    if (in) {
        edge zhx;   zhx.vl = 0; zhx.to = id;
        ed[tr[id].ls].push_back(zhx);
        ed[tr[id].rs].push_back(zhx);
    }
    else {
        edge zhx;   zhx.vl = 0;
        zhx.to = tr[id].ls; ed[id].push_back(zhx);
        zhx.to = tr[id].rs; ed[id].push_back(zhx);
    }
    return;
}

void getPointV(int id, int tgtl, int tgtr) {
    int l = tr[id].l;
    int r = tr[id].r;
    if (l >= tgtl && r <= tgtr) {
        stk.push(id);
        return;
    }
    int mid = l + r >> 1;
    if (mid < tgtl) getPointV(tr[id].rs, tgtl, tgtr);
    else if (mid >= tgtr) getPointV(tr[id].ls, tgtl, tgtr);
    else {
        getPointV(tr[id].ls, tgtl, tgtr);
        getPointV(tr[id].rs, tgtl, tgtr);
    }
    return;
}

void getPointU(int id, int tgtl, int tgtr, int EV) {
    int l = tr[id].l;
    int r = tr[id].r;
    if (l >= tgtl && r <= tgtr) {
        stk.makeEdge(id, EV);
        return;
    }
    int mid = l + r >> 1;
    if (mid < tgtl) getPointU(tr[id].rs, tgtl, tgtr, EV);
    else if (mid >= tgtr) getPointU(tr[id].ls, tgtl, tgtr, EV);
    else {
        getPointU(tr[id].ls, tgtl, tgtr, EV);
        getPointU(tr[id].rs, tgtl, tgtr, EV);
    }
    return;
}

void getAnswer(int id) {
    int l = tr[id].l;
    int r = tr[id].r;
    if (l == r) {
        printf("%lld ", cnnct[id] ? dis[id] : -1);
        return;
    }
    getAnswer(tr[id].ls);
    getAnswer(tr[id].rs);
    return;
}

//不要用SPFA!!会TLE!!#9直接挂了(不知道为什么会TLE,理论上这道题应该是稀疏图才对)
/*
void spfa(int ps) {
    memset(dis, 0x7f, sizeof(dis));
    dis[ps] = 0;
    qwq.push(ps);
    while (!qwq.empty()) {
        int x = qwq.front();
        cnnct[x] = true;
        for (int i = 0; i < ed[x].size(); i++) {
            int y = ed[x][i].to;
            int v = ed[x][i].vl;
            if (dis[y] < dis[x] + v) continue;
            dis[y] = dis[x] + v;
            qwq.push(y);
        }
        qwq.pop();
    }
    return;
}
*/

void dijkstra(int ps) {
    memset(dis, 0x7f, sizeof(dis));
    dis[ps] = 0;
    pqnode zhx;
    zhx.node = ps;
    zhx.ds = 0;
    pq.push(zhx);
    while (!pq.empty()) {
        int x = pq.top().node;
        int d = pq.top().ds;
        pq.pop();
        if (cnnct[x]) continue;
        cnnct[x] = true;
        for (int i = 0; i < ed[x].size(); i++) {
            int y = ed[x][i].to;
            int v = ed[x][i].vl;
            if (cnnct[y]) continue;
            dis[y] = min(dis[y], d + v);
            pqnode lxl;
            lxl.node = y;
            lxl.ds = dis[y];
            pq.push(lxl);
        }
    }
}

signed main() {
    scanf("%lld %lld %lld", &n, &q, &s);
    build(1, 1, n, 0, s);
    build(1 + nmaxin1, 1, n, 1, 0);
    for (int i = 1; i <= q; i++) {
        int opt, u, l, r, w;
        scanf("%lld", &opt);
        if (opt == 1) {
            scanf("%lld %lld %lld", &u, &r, &w);
            getPointV(1, r, r);
            getPointU(1 + nmaxin1, u, u, w);
            stk.clear();
        }
        else if (opt == 2) {
            scanf("%lld %lld %lld %lld", &u, &l, &r, &w);
            getPointV(1, l, r);
            getPointU(1 + nmaxin1, u, u, w);
            stk.clear();
        }
        else if (opt == 3) {
            scanf("%lld %lld %lld %lld", &u, &l, &r, &w);
            getPointV(1, u, u);
            getPointU(1 + nmaxin1, l, r, w);
            stk.clear();
        }
    }
    //不要用SPFA!!会TLE!!#9直接挂了(不知道为什么会TLE,理论上这道题应该是稀疏图才对)  × 2
    // spfa(pointStart);
    dijkstra(pointStart);
    getAnswer(1);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值