【笔记】优化建图
线段树优化建图
实际上就是利用了线段树结构将区间表示成 logN 个节点的性质,若与某个区间连同一个权值的边,做成连到对应线段树的节点上去
例题 CF786B Legacy
考虑建两个线段树,一个父亲到儿子连单向零边,一个儿子到父亲连单向零边。单点到区间连单向边的含义:单点可以从该区间向下走直到所包含的单点;区间向单点连单向边的含义:该区间包含的单点可以向上走再到该单点。
例题 [PA2011]Journeys
对于段到段上点两两连单向边,设置一个中间节点,从一段到中间节点连一簇有权值的单向边,中间节点到另一段连一簇单向零边就行了。
实现细节:多开一个 1~N 的单点,和两个线段树的底层分别连无向零边,而对单点间的连边和其他一些操作就在这个上面做。另外动态开点,记录 ls 和 rs 。
关键代码
void build1(int x, int l, int r)
{
if (l==r) adde(x, l, 0, ++tote), adde(l, x, 0, ++tote);
else {
int mid = (l + r) >> 1;
ls[x] = ++totn, adde(x, ls[x], 0, ++tote), build1(ls[x], l, mid);
rs[x] = ++totn, adde(x, rs[x], 0, ++tote), build1(rs[x], mid+1,r);
}
}
void build2(int x, int l, int r)
{
if (l==r) adde(x, l, 0, ++tote), adde(l, x, 0, ++tote);
else {
int mid = (l + r) >> 1;
ls[x] = ++totn, adde(ls[x], x, 0, ++tote), build2(ls[x], l, mid);
rs[x] = ++totn, adde(rs[x], x, 0, ++tote), build2(rs[x], mid+1,r);
}
}
void bridge1(int x, int l, int r, int _l, int _r, int p)
{
if (l>=_l&&r<=_r) { adde(p, x, 1, ++tote); }
else {
int mid = (l + r) >> 1;
if (mid>=_l) bridge1(ls[x], l, mid, _l, _r, p);
if (mid< _r) bridge1(rs[x], mid+1, r, _l, _r, p);
}
}
void bridge2(int x, int l, int r, int _l, int _r, int p)
{
if (l>=_l&&r<=_r) { adde(x, p, 0, ++tote); }
else {
int mid = (l + r) >> 1;
if (mid>=_l) bridge2(ls[x], l, mid, _l, _r, p);
if (mid< _r) bridge2(rs[x], mid+1, r, _l, _r, p);
}
}
以及
rt1 = totn = N+1, build1(rt1, 1, N);
rt2 = ++totn, build2(rt2, 1, N);
...
adde(u, v, w, ++tote);
...
totn++;
bridge1(rt1, 1, N, a, b, totn), bridge2(rt2, 1, N, c, d, totn);
totn++;
bridge1(rt1, 1, N, c, d, totn), bridge2(rt2, 1, N, a, b, totn);
...
[SNOI2017]炸弹 :线段树优化建图 + tarjan 缩点,另外注意的是(因为这个是没想到的)那个 DAG 上的经典问题:求当前节点之前所有节点的权值和,似乎见过的题都是依靠一些性质做,本身做不了。
倍增优化建图
个人写法
例题: luoguP5344 【XR-1】逛森林
这里考虑一棵树,对于其两条简单路径上所有点连边的问题
建图:seg[i][t] 表示节点 i 向上 \(2^t\) 个节点(包括 i 本身)的这一段,那么可以有( dp 数组含义是祖先节点):
adde(seg1[x][i-1], seg1[x][i], 0, ++tote);
adde(seg1[dp[x][i-1]][i-1], seg1[x][i], 0, ++tote);
和
adde(seg2[x][i], seg2[x][i-1], 0, ++tote);
adde(seg2[x][i], seg2[dp[x][i-1]][i-1], 0, ++tote);
实现细节:多开一个 1~N 的节点,方便在上面跑树相关的操作
void build1(int x, int f, int d)
{
vis[x] = 1, dep[x] = d, dp[x][0] = f, seg1[x][0] = ++totn;
adde(x, totn, 0, ++tote), adde(totn, x, 0, ++tote);
for (int i=1; i<=lg[d]; i++) {
dp[x][i] = dp[dp[x][i-1]][i-1];
seg1[x][i] = ++totn;
adde(seg1[x][i-1], seg1[x][i], 0, ++tote);
adde(seg1[dp[x][i-1]][i-1], seg1[x][i], 0, ++tote);
}
for (int o=fst[x]; o; o=e[o].pre)
if (e[o].v<=N && e[o].v!=f) build1(e[o].v, x, d+1);
}
void build2(int x, int f, int d)
{
dep[x] = d, seg2[x][0] = ++totn;
adde(x, totn, 0, ++tote), adde(totn, x, 0, ++tote);
for (int i=1; i<=lg[d]; i++) {
//dp[x][i] = dp[dp[x][i-1]][i-1];
seg2[x][i] = ++totn;
adde(seg2[x][i], seg2[x][i-1], 0, ++tote);
adde(seg2[x][i], seg2[dp[x][i-1]][i-1], 0, ++tote);
}
for (int o=fst[x]; o; o=e[o].pre)
if (e[o].v<=N && e[o].v!=f) build2(e[o].v, x, d+1);
}
int lca(int a, int b)
{
if (dep[a] < dep[b]) a ^= b ^= a ^= b;
while (dep[a] > dep[b]) a = dp[a][lg[dep[a]-dep[b]]];
if (a == b) return a;
for (int i=lg[dep[a]]; i>=0; i--)
if (dp[a][i] != dp[b][i]) a = dp[a][i], b = dp[b][i];
return dp[a][0];
}
void bridge(int u1, int v1, int c1, int u2, int v2, int c2, int w)
{
totn++; //printf("[%d]\n", totn);
//printf("%d (%d) %d --> %d (%d) %d\n", u1,c1,v1,u2,c2,v2);
for (int i=u1; i!=c1; i=dp[i][lg[dep[i]-dep[c1]]])
adde(seg1[i][lg[dep[i]-dep[c1]]], totn, w, ++tote);
for (int i=v1; i!=c1; i=dp[i][lg[dep[i]-dep[c1]]])
adde(seg1[i][lg[dep[i]-dep[c1]]], totn, w, ++tote);
adde(seg1[c1][0], totn, w, ++tote);
for (int i=u2; i!=c2; i=dp[i][lg[dep[i]-dep[c2]]])
adde(totn, seg2[i][lg[dep[i]-dep[c2]]], 0, ++tote);
for (int i=v2; i!=c2; i=dp[i][lg[dep[i]-dep[c2]]])
adde(totn, seg2[i][lg[dep[i]-dep[c2]]], 0, ++tote);
adde(totn, seg2[c2][0], 0, ++tote);
}
以及
totn = N;
//build1(S, 0, 0), build2(S, 0, 0);
for (int i=1; i<=N; i++) if (!vis[i]) build1(i, 0, 0), build2(i, 0, 0);
...
for (int i=1; i<=totp; i++) {
int c1 = lca(P[i].u1, P[i].v1), c2 = lca(P[i].u2, P[i].v2);
bridge(P[i].u1, P[i].v1, c1, P[i].u2, P[i].v2, c2, P[i].w); // 这里只建了一段到另一段的单向边
}