(广义)圆方树

圆方树用于解决仙人掌(没有一条边在两个环上的无向连通图)上的一些问题,广义圆方树用于解决无向图中的一些问题。(不过目前没怎么做过题,不知道广义不广义有啥区别)

博客

前置技能

tarjan 算法求点双

一定要先学好 tarjan 算法啊!! 吃够了基础不牢的苦

圆方树的构建

和 tarjan 点双缩点不一样的是,我们并不去“复制”割点,甚至都不用显式地标出割点,我们直接用一个“方点”代表一个DCC,“方点”向DCC上的所有点连边,以保证每个点只出现一次。特别的,当DCC中只有两个点的时候,我们更倾向于不建“方点”,因为这种情况不会出环,只是个没有割点的DCC

至于边权,就可能是具体问题具体分析了。(因为我目前就做过一道题)。比如P5236 【模板】静态仙人掌
这道例题,求仙人掌上两点最短路,我们的处理方法是:圆圆边(圆点-圆点)边权就是原图边权;圆方边边权是圆点到方点的父亲(即DCC里面靠上的那个割点)在原图中的最短距离。查询的话用树剖lca的方法解决,如果lca是圆点,就照常做;如果是方点,就要把经过那个环的两种路径都考虑一下。

总之,细节很多,需要一些技巧。不过如果不考虑边权的话,圆方树还是比较友好的。

代码:(比较复杂)

void tarjan(int cur) {
	dfn[cur] = low[cur] = ++dcnt;
	stk[++stop] = cur;
	for (register int i = head[cur]; i; i = e[i].nxt) {
		int to = e[i].to;
		if (!dfn[to]) {
			pre_val[to] = e[i].val;
			tarjan(to);
			MIN(low[cur], low[to]);
			if (low[to] >= dfn[cur]) {
				if (stk[stop - 1] == cur) {
					--stop;
					Addedge(cur, to, e[i].val);
					Addedge(to, cur, e[i].val);
					continue;
				}
				++dtot;
				int p = stk[stop], S = lval[p];
				for (register int j = stop; stk[j] != cur; --j)
					sum[stk[j]] = S, S += pre_val[stk[j]];
				while (stk[stop] != cur) {//注意为了适应广义圆方树,这里最好也写成 lst != to
					int tmp = stk[stop--];
					int evl = min(sum[tmp], S - sum[tmp]);
					Addedge(tmp, dtot, evl);
					Addedge(dtot, tmp, evl);
				}
				Addedge(cur, dtot, 0), Addedge(dtot, cur, 0); sum[cur] = 0;
				sum[dtot] = S;
			}
		} else {
			if (dfn[to] < low[cur])	low[cur] = dfn[to], lval[cur] = e[i].val;
		}
	}
}
int Find(int x, int lca) {//找到 x - lca 路径中lca下面的那个点的编号
	int lst = 0;
	while (top[x] != top[lca])	lst = top[x], x = fa[top[x]];
	return x == lca ? lst : son[lca];
}

...

int main() {
      ...      
                int lca = get_lca(u, v);
		if (lca <= n)
			printf("%d\n", dis[u] + dis[v] - (dis[lca] << 1));
		else {
			int ans = dis[u] + dis[v];
			u = Find(u, lca), v = Find(v, lca);
			ans = ans - dis[u] - dis[v];
			if (sum[u] < sum[v])	swap(u, v);
			ans += min(sum[u] - sum[v], sum[lca] - (sum[u] - sum[v]));
			printf("%d\n", ans);
		}
      ...
}

例题

tourists

给一张简单无向连通图,要求支持单点修改点权和查询 \(x\)\(y\) 的所有简单路径上的点权的最小值。\(n,m,q \le 10^5\)

简单路径的定义为不经过重点,于是可以想到点双缩点或者广义圆方树。

建出圆方树(或点双缩点成树),\(x \to y\) 的所有路径上的点的最小值实际上就是链上的点(dcc)的最小值。于是可以将方点点权设为该dcc上的点权最小值,树剖查询即可。

现还要求支持单点修改。我们每修改一个点的点权后,还要相应地修改所有与其相邻的方点的点权。暴力修改显然复杂度是错的,于是可以使用 Fusion tree 的经典套路,我们只将点的贡献计算给其父亲,这样的话方点会拥有所有儿子的信息,缺失父亲的信息,用的时候查一下即可。每次只有 lca 可能会确实信息,于是复杂度是正确的。

点双的做法对于dcc维护最小值,将割点及dcc内的非割点的信息统计好。非割点的复杂度没问题,割点的方法与圆方树的方法类似。

战略游戏

没时间写了,以后再说。

模板(调试用)

//tourists
int dfn[N], dcnt, low[N];
int stk[N], stop;
void tarjan(int cur) {
	dfn[cur] = low[cur] = ++dcnt;
	stk[++stop] = cur;
	for (int i= head[cur]; i; i = e[i].nxt) {
		int to = e[i].to;
		if (!dfn[to]) {
			tarjan(to); MIN(low[cur], low[to]);
			if (low[to] >= dfn[cur]) {
				++ptot;
				int tmp;
				do {
					tmp = stk[stop--];
					E[++Ecnt] = (Edge){tmp, ptot};
				} while (tmp != to);
				E[++Ecnt] = (Edge){cur, ptot};
			}
		} else MIN(low[cur], dfn[to]);
	}
}
posted @ 2020-07-08 22:43  JiaZP  阅读(246)  评论(0编辑  收藏  举报