【笔记】优化建图

线段树优化建图

实际上就是利用了线段树结构将区间表示成 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); // 这里只建了一段到另一段的单向边
}
posted @ 2021-07-19 16:55  zrkc  阅读(91)  评论(0)    收藏  举报