专题:优化建图
线段树优化建图
\(n\) 个点,\(m\) 次连边,求最短路。
- \(u\) 连向 \(v\)。
- \(u\) 连向 \([l, r]\)。
- \([l, r]\) 连向 \(u\)。
操作二:单点向线段连边,且线段存在向下的边走到叶子(绿色边)。
操作三:线段向单点连边,且叶子存在向上的边走到线段(蓝色边)。
一棵树做两个操作会矛盾,开两棵线段树。
树 \(1\) 父亲连向儿子,操作二即叶子连向树 \(1\) 线段(橙色边)。(叶子不作区分)
树 \(2\) 儿子连向父亲,操作三即树 \(2\) 线段连向叶子(粉色边)。
两棵树的叶子可视为同一点,对应点间连边权为 \(0\) 的无向边(也可以是 \(1\to2\) 的有向边)。
#include<bits/stdc++.h>
#define eb emplace_back
#define ep emplace
using namespace std;
using ll = long long;
constexpr int N = 2 * 1e5 + 5;
int n, q, s, Leaf[N];
vector<pair<int, int>> G[N << 2];
#define c(x, v) ((x) * 2 + (v))
#define ls x << 1
#define rs ls | 1
void build(int x = 1, int l = 1, int r = n) {
if(l == r) {
Leaf[l] = c(x, 0);
G[c(x, 0)].eb(c(x, 1), 0);
return;
}
int mid = l + r >> 1;
build(ls, l, mid);
build(rs, mid + 1, r);
G[c(x, 0)].eb(c(ls, 0), 0), G[c(x, 0)].eb(c(rs, 0), 0);
G[c(ls, 1)].eb(c(x, 1), 0), G[c(rs, 1)].eb(c(x, 1), 0);
}
void add(int p, int L, int R, int w, int type, int x = 1, int l = 1, int r = n) {
if(L <= l && r <= R) {
if(type == 2) {
G[Leaf[p]].eb(c(x, 0), w);
}
else {
G[c(x, 1)].eb(Leaf[p], w);
}
return;
}
int mid = l + r >> 1;
if(L <= mid) add(p, L, R, w, type, ls, l, mid);
if(R > mid) add(p, L, R, w, type, rs, mid + 1, r);
}
ll dist[N << 2];
bool st[N << 2];
int main() {
cin.tie(0)->sync_with_stdio(0);
cin >> n >> q >> s;
build();
for(int i = 1; i <= q; ++ i) {
int op;
cin >> op;
if(op == 1) {
int u, v, w;
cin >> u >> v >> w;
G[Leaf[u]].eb(Leaf[v], w);
}
else {
int u, l, r, w;
cin >> u >> l >> r >> w;
add(u, l, r, w, op);
}
}
memset(dist, 0x3f, sizeof dist);
dist[Leaf[s]] = 0;
priority_queue<pair<ll, int>> q;
q.ep(0, Leaf[s]);
while(!q.empty()) {
int x = q.top().second;
q.pop();
if(st[x]) continue;
st[x] = 1;
for(auto [y, z] : G[x]) {
if(dist[y] > dist[x] + z) {
dist[y] = dist[x] + z;
q.ep(-dist[y], y);
}
}
}
for(int i = 1; i <= n; ++ i) {
if(dist[Leaf[i]] > 1e18) {
cout << -1 << ' ';
}
else {
cout << dist[Leaf[i]] << ' ';
}
}
return 0;
}
点爆一颗炸弹可以点爆所有在其半径内的炸弹。
把第 \(i\) 个炸弹引爆,将引爆多少个炸弹呢?
爆炸范围是连续的。
如果 \(x\) 能引爆 \(y\),则可以用 \(y\) 的左右边界更新 \(x\) 的左右边界。
也就是说可以在缩点后根据反拓扑序求出答案。
只需要单点向区间连边,建一棵下向树足以。
#include<bits/stdc++.h>
#define eb emplace_back
#define ep emplace
using namespace std;
using ll = long long;
constexpr int N = 1e6 + 5, M = N << 2, P = 1e9 + 7;
int n;
struct Bomb{
ll p, r;
} b[N];
int binary_serch(int i, int l, int r, int type) {
if(!type) {
while(l < r) {
int mid = l + r >> 1;
if(b[i].p - b[mid].p <= b[i].r) r = mid;
else l = mid + 1;
}
}
else {
while(l < r) {
int mid = l + r + 1 >> 1;
if(b[mid].p - b[i].p <= b[i].r) l = mid;
else r = mid - 1;
}
}
return l;
}
#define ls x << 1
#define rs ls | 1
int Leaf[N];
vector<int> G[M];
vector<pair<int, int>> e;
int t[M];
void addedge(int x, int y) {
G[x].eb(y), e.eb(x, y);
}
void build(int x = 1, int l = 1, int r = n) {
if(l == r) {
t[x] = l;
Leaf[l] = x;
return;
}
int mid = l + r >> 1;
build(ls, l, mid), build(rs, mid + 1, r);
addedge(x, ls);
addedge(x, rs);
}
void add(int p, int L, int R, int x = 1, int l = 1, int r = n) {
if(L <= l && r <= R) {
addedge(Leaf[p], x);
return;
}
int mid = l + r >> 1;
if(mid >= L) add(p, L, R, ls, l, mid);
if(mid < R) add(p, L, R, rs, mid + 1, r);
}
int dfn[M], low[M], ts, stk[M], ins[M], tp, id[M], L[N], R[N], tot, deg[M];
vector<int> H[M];
void Tarjan(int x) {
dfn[x] = low[x] = ++ ts;
ins[stk[++ tp] = x] = 1;
for(int y : G[x]) {
if(!dfn[y]) {
Tarjan(y);
low[x] = min(low[x], low[y]);
}
else if(ins[y]) {
low[x] = min(low[x], dfn[y]);
}
}
if(low[x] == dfn[x]) {
int y = 0;
L[++ tot] = n + 1;
while(y != x) {
ins[y = stk[tp --]] = 0;
id[y] = tot;
if(t[y]) {
L[tot] = min(L[tot], t[y]);
R[tot] = max(R[tot], t[y]);
}
}
}
}
int main() {
cin.tie(0)->sync_with_stdio(0);
cin >> n;
for(int i = 1; i <= n; ++ i) {
cin >> b[i].p >> b[i].r;
}
build();
for(int i = 1; i <= n; ++ i) {
int l = binary_serch(i, 1, i, 0);
int r = binary_serch(i, i, n, 1);
add(i, l, r);
}
Tarjan(1);
for(auto [i, j] : e) {
if(id[i] != id[j]) {
H[id[j]].eb(id[i]);
++ deg[id[i]];
}
}
queue<int> q;
for(int i = 1; i <= tot; ++ i) {
if(!deg[i])
q.push(i);
}
while(!q.empty()) {
int x = q.front();
q.pop();
for(int y : H[x]) {
L[y] = min(L[y], L[x]);
R[y] = max(R[y], R[x]);
if(!-- deg[y]) {
q.push(y);
}
}
}
ll ans = 0;
for(int i = 1; i <= n; ++ i) {
int o = id[Leaf[i]];
ans = (ans + i * ll(R[o] - L[o] + 1)) % P;
}
cout << ans;
return 0;
}
P6348 [PA2011] Journeys
区间向区间连边,求最短路。
区间与区间连边很容易想到建一个虚点。
对于无向边,需要建两个虚点,否则会建一条自己走到自己的路径,使得该区间任意节点相互到达。
下图为 \([1, 2]\) 连向 \([3, 4]\) 的示例。
#include<bits/stdc++.h>
#define eb emplace_back
#define ep emplace
using namespace std;
using ll = long long;
constexpr int N = 1e6 + 5, M = N << 2;
#define c(x, v) ((x) * 2 + v)
#define ls x << 1
#define rs ls | 1
int n, m, s, Leaf[N], idx;
vector<pair<int, int>> G[M];
void add(int u, int v, int w) {
G[u].eb(v, w);
}
void build(int x = 1, int l = 1, int r = n) {
idx = max(idx, c(x, 1)); // 虚点指针
if(l == r) {
Leaf[l] = c(x, 0);
add(c(x, 0), c(x, 1), 0);
return;
}
int mid = l + r >> 1;
build(ls, l, mid);
build(rs, mid + 1, r);
add(c(x, 0), c(ls, 0), 0), add(c(ls, 1), c(x, 1), 0);
add(c(x, 0), c(rs, 0), 0), add(c(rs, 1), c(x, 1), 0);
}
vector<int> get(int L, int R, int x = 1, int l = 1, int r = n) {
if(L <= l && r <= R) {
return vector<int>{x};
}
int mid = l + r >> 1;
if(R <= mid) {
return get(L, R, ls, l, mid);
}
if(L > mid) {
return get(L, R, rs, mid + 1, r);
}
auto a = get(L, R, ls, l, mid);;
auto b = get(L, R, rs, mid + 1, r);
if(a.size() < b.size()) {
swap(a, b);
}
for(auto x : b) {
a.eb(x);
}
return a;
}
int d[M], v[M];
int main() {
cin.tie(0)->sync_with_stdio(0);
cin >> n >> m >> s;
build();
for(int i = 1; i <= m; ++ i) {
int l, r, L, R;
cin >> l >> r >> L >> R;
++ idx;
for(auto x : get(l, r)) {
add(c(x, 1), idx, 0), add(idx + 1, c(x, 0), 0);
}
for(auto x : get(L, R)) {
add(idx, c(x, 0), 1), add(c(x, 1), idx + 1, 1);
}
++ idx;
}
memset(d, 0x3f, sizeof d);
d[Leaf[s]] = 0;
priority_queue<pair<ll, int>> q;
q.ep(0, Leaf[s]);
while(!q.empty()) {
int x = q.top().second;
q.pop();
if(v[x]) continue;
v[x] = 1;
for(auto [y, z] : G[x]) {
if(d[y] > d[x] + z) {
d[y] = d[x] + z;
q.ep(-d[y], y);
}
}
}
for(int i = 1; i <= n; ++ i) {
cout << d[Leaf[i]] << '\n';
}
return 0;
}