线段树优化建图
前言
鸣谢 ckj 学长,yyds
引子
挺简单但是很实用的一种建图方法,可以将 \(O(n^2)\) 的建图优化到 \(O(n~log n)\)
先看一道例题 CF786B Legacy
题目大意:有 \(n\) 个点、\(q\) 次操作。每一种操作为以下三种类型中的一种:
- 操作一:连一条 \(u→v\) 的有向边,权值为 \(w\)
- 操作二:对于所有 \(i∈[l,r]\) 连一条 \(u→i\) 的有向边,权值为 \(w\)。
- 操作三:对于所有 \(i∈[l,r]\) 连一条 \(i→u\) 的有向边,权值为 \(w\)。
求从点 \(s\) 到其他点的最短路。\(1≤n,q≤10^5,1≤w≤10^9\)
mind
首先想到的肯定是暴力建图,每一次操作 \(O(n)\) 扫一边,显然会超时
因为是对一个区间进行操作,所以考虑用线段树来搞点事情
建两棵线段树,一棵为"出树"(所有边指向儿子),一棵是 "入树" (所有边指向父亲)
对向区间建边的,可以在线段树上找到这段区间,然后直接指向这段区间就好了
具体操作可以看这 讲的超级详细
学的时候唯一难理解的是实际点的编号是如何和线段树中编号怎么避免冲突的?
其实每个点都对应着线段树中的一个叶子节点,对于每个点的实际标号,可以理解为线段树中是第几个叶子,然后它肯定要对应线段树的一个标号,而我们建图是只利用线段树的编号进行建图,所以每一个点都要对应到线段树上的点才能连边(建图),这样就保证了只会用到线段树的编号,避免了矛盾
code
/*
work by:Ariel_
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#define int long long
#define lson rt << 1
#define rson rt << 1|1
using namespace std;
const int N = 3e6 + 5, K = 5e5 + 5;
int read() {
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9'){if (c == '-') f = -1;c = getchar();}
while(c >= '0' && c <= '9') {x = x * 10 + c - '0';c = getchar();}
return x * f;
}
int n, m, s, opt, a[N];
struct edge{int v, nxt, w;}e[N];
int head[N], E;
void add_edge(int u, int v, int w) {
e[++E] = (edge) {v, head[u], w};
head[u] = E;
}
namespace Seg{
void build(int rt, int l, int r) {
if (l == r) {a[l] = rt; return;}
int mid = (l + r) >> 1;
add_edge(rt, rt << 1, 0), add_edge(rt, rt << 1 | 1, 0);
add_edge((rt << 1) + K, rt + K, 0), add_edge((rt << 1 | 1) + K, rt + K, 0);
build(lson, l, mid), build(rson, mid + 1, r);
}
void modify(int rt, int l, int r, int L, int R, int v, int w) {
if (L <= l && r <= R) {
if (opt == 2) add_edge(v + K, rt, w);
else add_edge(rt + K, v, w);
return;
}
int mid = (l + r) >> 1;
if (L <= mid) modify(lson, l, mid, L, R, v, w);
if (R > mid) modify(rson, mid + 1, r, L, R, v, w);
}
}
using namespace Seg;
priority_queue<pair<int, int> > q;
int dis[N], vis[N];
void dij(int s) {
memset(dis, 0x3f, sizeof dis), dis[s] = 0;
q.push(make_pair(0, s));
while(q.size()) {
int u = q.top().second; q.pop();
if (vis[u]) continue;
vis[u] = 1;
for (int i = head[u]; i; i = e[i].nxt) {
int v = e[i].v;
if (e[i].w + dis[u] < dis[v]) {
dis[v] = e[i].w + dis[u];
q.push(make_pair(-dis[v], v));//默认从小到大排序,所以要取负号
}
}
}
}
signed main(){
n = read(), m = read(), s = read();
build(1, 1, n);
for (int i = 1; i <= n; i++) add_edge(a[i], a[i] + K, 0), add_edge(a[i] + K, a[i], 0);
for (int i = 1, x, y, z, l, r, w; i <= m; i++) {
opt = read();
if (opt == 1) {
x = read(), y = read(), z = read();//这里的 x, y 对应线段树的第几个叶子节点,a[x] 存的是线段树中第 x 叶子节点的标号
add_edge(a[x] + K, a[y], z);
}
else {
x = read(), l = read(), r = read(), w = read();
modify(1, 1, n, l, r, a[x], w);
}
}
dij(a[s] + K);
for (int i = 1; i <= n; i++)
printf("%lld%c",dis[a[i]]!=0x3f3f3f3f3f3f3f3fll?dis[a[i]]:-1,i==n?'\n':' ');
puts("");
return 0;
}
写在后面
关于优化建图的方式还有分块优化建图,KD 树优化建图,主席树连边……
争取退役前干完 QWQ