「笔记」线段树优化建图
写在前面
Q:为什么新开一个博文?
A:因为魔理沙(?
介绍
用于解决区间连边的一个建图小技巧。
对于区间连边问题,其解决方案是建立一系列虚点,虚点到实点的权值为 0,一个虚点与某一段连续区间内的实点相连。此时若想对该区间进行区间连边,直接连向此虚点即可。
为解决区间的拆分问题,可以将虚点建成一个如下图所示的类似线段树的结构,使得区间的拆分可以放到线段树上进行:
图中 \(1,2,3,4\) 为实点,\(5,6,7\) 为虚点。对于连边操作 \((1, [2,4])\),可以仅令 \(1\) 连向点 \(2\),点 \(6\),从而减少了连边数。
上面的线段树的虚边是自顶向底连接的,可以解决单点向区间连边的问题。如果要实现区间向点连边,建立一棵自底向顶连边的线段树并进行上述过程即可。
关于复杂度,建立虚点后图中节点个数为 \(n + 2n\log n\)。
每次在线段树上区间连边,会新增不多于 \(\log n\) 条边。总边数为 \(O(n\log n)\) 级别。
CF786B Legacy
给定 \(n\) 个节点,给定参数 \(s\),有 \(m\) 次操作:
- 给定参数 \(u,v,w\),从 \(u\) 向 \(v\) 连一条权值为 \(w\) 的边。
- 给定参数 \(u,l,r,w\),从 \(u\) 向 \([l,r]\) 连一条权值为 \(w\) 的边。
- 给定参数 \(u,l,r,w\),从 \([l,r]\) 向 \(u\) 连一条权值为 \(w\) 的边。
求 \(s\) 到每个节点的最短路。
\(1\le n,q\le 10^5\),\(1\le w\le 10^9\)。
2S,256MB。
线段树优化建图模板,在新图上跑最短路即可。
新图的点边数是 \(O(n\log n)\) 级别,最短路的时间复杂度为 \(O(n\log^2 n)\) 级别。算法总时间复杂度 \(O(n\log^2 n)\),空间复杂度 \(O(n\log n)\)。
//知识点:线段树优化建图
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <queue>
#define LL long long
const int kN = 4e5 + 10;
const LL kInf = 0x3f3f3f3f3f3f3f3f;
//=============================================================
int n, q, start, node_num;
int e_num, head[kN], v[kN << 4], w[kN << 4], ne[kN << 4];
bool vis[kN];
LL dis[kN];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) {
w = (w << 3) + (w << 1) + (ch ^ '0');
}
return f * w;
}
void Chkmax(int &fir_, int sec_) {
if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
if (sec_ < fir_) fir_ = sec_;
}
void Add(int u_, int v_, int w_) {
v[++ e_num] = v_, w[e_num] = w_;
ne[e_num] = head[u_], head[u_] = e_num;
}
#define ls (lson[now_])
#define rs (rson[now_])
#define mid ((L_+R_)>>1)
struct SegmentTree {
int root, lson[kN], rson[kN];
void Build(int &now_ ,int L_, int R_, bool type_) {
if (L_ == R_) {
now_ = L_;
return ;
}
now_ = ++ node_num;
Build(ls, L_, mid, type_);
Build(rs, mid + 1, R_, type_);
if (!type_) Add(now_, ls, 0), Add(now_, rs, 0);
if (type_) Add(ls, now_, 0), Add(rs, now_, 0);
}
void Modify(int now_, int L_, int R_, int l_, int r_, int u_, int w_,
bool type_) {
if (l_ <= L_ && R_ <= r_) {
if (!type_) Add(u_, now_, w_);
if (type_) Add(now_, u_, w_);
return ;
}
if (l_ <= mid) Modify(ls, L_, mid, l_, r_, u_, w_, type_);
if (r_ > mid) Modify(rs, mid + 1, R_, l_, r_, u_, w_, type_);
}
} Seg[2];
#undef ls
#undef rs
#undef mid
void Init() {
node_num = n;
Seg[0].Build(Seg[0].root, 1, n, 0);
Seg[1].Build(Seg[1].root, 1, n, 1);
}
void Spfa(int s_) { //他死了
std::queue <int> q;
memset(dis, 0x3f, sizeof(dis));
dis[s_] = 0;
q.push(s_);
while (!q.empty()) {
int u = q.front();
q.pop();
for (int i = head[u]; i; i = ne[i]) {
int v_ = v[i], w_ = w[i];
if (dis[u] + w_ < dis[v_]) {
dis[v_] = dis[u] + w_;
q.push(v_);
}
}
}
}
#define pr std::pair
#define mp std::make_pair
void Dijkstra(int s_) {
std::priority_queue <pr <LL, int> > q;
memset(dis, 63, sizeof (dis));
dis[s_] = 0;
q.push(mp(0, s_));
while (! q.empty()) {
int u_ = q.top().second; q.pop();
if (vis[u_]) continue;
vis[u_] = true;
for (int i = head[u_]; i; i = ne[i]) {
int v_ = v[i], w_ = w[i];
if (dis[u_] + w_ < dis[v_]) {
dis[v_] = dis[u_] + w_;
q.push(mp(-dis[v_], v_));
}
}
}
}
#undef pr
#undef mp
//=============================================================
int main() {
n = read(), q = read(), start = read();
Init();
while (q --) {
int opt = read();
if (opt == 1) {
int u_ = read(), v_ = read(), w_ = read();
Add(u_, v_, w_);
} else {
int u_ = read(), l_ = read(), r_ = read(), w_ = read();
int type = (opt == 3);
Seg[type].Modify(Seg[type].root, 1, n, l_, r_, u_, w_, type);
}
}
Dijkstra(start);
for (int i = 1; i <= n; ++ i) {
printf("%lld ", dis[i] < kInf ? dis[i] : -1);
}
return 0;
}
P6348 [PA2011]Journeys
给定 \(n\) 个点,有 \(m\) 次操作。每次操作给定参数 \(a,b,c,d\),表示区间 \([a,b]\) 内所有点与区间 \([c,d]\) 内所有点两两间有一条双向的权值为 1 的边。
给定起点,求起点到每个点的最短路。
\(1\le n\le 5\times 10^5\),\(1\le m\le 10^5\)。
3S,512MB。
区间向区间连边,先进行一次区间拆分,再将拆分出的节点进行单点向区间连边即可。边数变为 \(O(n\log^2 n)\) 级别。
注意双向边的处理,详见代码。
//知识点:线段树优化建图
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <queue>
#define LL long long
const int kN = 2e6 + 10;
const int kM = 5e7 + 10;
const LL kInf = 0x3f3f3f3f3f3f3f3f;
//=============================================================
int n, m, start, node_num;
int e_num, head[kN], v[kM], ne[kM];
bool w[kM], vis[kN];
LL dis[kN];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) {
w = (w << 3) + (w << 1) + (ch ^ '0');
}
return f * w;
}
void Chkmax(int &fir_, int sec_) {
if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
if (sec_ < fir_) fir_ = sec_;
}
void Add(int u_, int v_, int w_) {
v[++ e_num] = v_, w[e_num] = w_;
ne[e_num] = head[u_], head[u_] = e_num;
}
#define ls (lson[now_])
#define rs (rson[now_])
#define mid ((L_+R_)>>1)
struct SegmentTree {
int root, lson[kN], rson[kN];
void Build(int &now_ ,int L_, int R_, bool type_) {
if (L_ == R_) {
now_ = L_;
return ;
}
now_ = ++ node_num;
Build(ls, L_, mid, type_);
Build(rs, mid + 1, R_, type_);
if (!type_) Add(now_, ls, 0), Add(now_, rs, 0);
if (type_) Add(ls, now_, 0), Add(rs, now_, 0);
}
void Modify(int now_, int L_, int R_, int l_, int r_, int u_) {
if (l_ <= L_ && R_ <= r_) {
Add(u_, now_, 1);
return ;
}
if (l_ <= mid) Modify(ls, L_, mid, l_, r_, u_);
if (r_ > mid) Modify(rs, mid + 1, R_, l_, r_, u_);
}
} Seg[2]; //0 自上向下连边, 1 自下向上连边。
#define lson Seg[1].lson
#define rson Seg[1].rson
void Modify2(int now_, int L_, int R_, int l1_, int r1_, int l2_, int r2_) { //在自下向上的线段树上拆分
if (l1_ <= L_ && R_ <= r1_) {
Seg[0].Modify(Seg[0].root, 1, n, l2_, r2_, now_);
return ;
}
if (l1_ <= mid) Modify2(ls, L_, mid, l1_, r1_, l2_, r2_);
if (r1_ > mid) Modify2(rs, mid + 1, R_, l1_, r1_, l2_, r2_);
}
#undef ls
#undef rs
#undef mid
void Init() {
node_num = n;
Seg[0].Build(Seg[0].root, 1, n, 0);
Seg[1].Build(Seg[1].root, 1, n, 1);
}
#define pr std::pair
#define mp std::make_pair
void Dijkstra(int s_) {
std::priority_queue <pr <LL, int> > q;
memset(dis, 63, sizeof (dis));
dis[s_] = 0;
q.push(mp(0, s_));
while (! q.empty()) {
int u_ = q.top().second; q.pop();
if (vis[u_]) continue;
vis[u_] = true;
for (int i = head[u_]; i; i = ne[i]) {
int v_ = v[i], w_ = w[i];
if (dis[u_] + w_ < dis[v_]) {
dis[v_] = dis[u_] + w_;
q.push(mp(-dis[v_], v_));
}
}
}
}
#undef pr
#undef mp
//=============================================================
int main() {
n = read(), m = read(), start = read();
Init();
for (int i = 1; i <= m; ++ i) {
int l1 = read(), r1 = read(), l2 = read(), r2 = read();
Modify2(Seg[1].root, 1, n, l1, r1, l2, r2); //两次先拆分的区间不同
Modify2(Seg[1].root, 1, n, l2, r2, l1, r1);
}
Dijkstra(start);
for (int i = 1; i <= n; ++ i) printf("%lld\n", dis[i]);
return 0;
}