数据结构优化建图

数据结构优化建图

有的时候我们需要将编号在 \([L, R]\) 中的每一个点都向编号在 \([L', R']\) 中的每一个点连边。这时暴力连边会使复杂度达到 \(O(n^2m)\) 的级别,因此我们需要对区间分块统一处理。

我们需要建立一棵入树和一棵出树。入树中的节点表示某些边可以连到区间中的任意点;出树的节点表示区间中的所有点都能向外连某些边。

首先我们对这些块与块之间进行处理:

  • 入树的所有父亲向儿子连0边(能到达\([1, 10]\),必然能到达 \([1, 5]\)\([6, 10]\));

  • 出树的所有儿子向父亲连0边(能从 \([1, 5]\) 中的某些点出发,必然能从 \([1, 10]\) 的某些点出发);

  • 入树的所有点向出树的对应点连0边(能到达\([1, 10]\)的所有点,必然能从\([1, 10]\)中的某些点出发)。

(简而言之,入树是一棵外向树,出树是一棵内向树,树树连边)

然后当我们连边 \([l, r] -> [l', r']\)的时候,我们把出树中的 \([l, r]\) 分成若干段,连向一个中转节点 \(p_i\),然后将 \(p_i\) 连向入树中的 \([l',r']\) 所含的若干段。

最后把整体当作一个图来处理即可。能保证最终的所有叶子节点的信息正确。(所有操作从入点开始做)

因为入树都向出树对应节点连无关紧要的边,使得出树的信息最终会被入树信息更新,但是不保证入树信息被出树信息更新。因此我们只需从入树开始,就能保证最终两棵树信息一致。

例题:CF786B Legacy

题意:支持一对多连边,求单源最短路。

模板题。直接找到入树中的 \(s\) 节点开始跑最短路即可。最终入树中的叶子节点和出树中的叶子节点的信息都是正确的。

例题:P3588 [POI2015]PUS

题意:已知一个序列的部分数,以及知道某些区间中的某些数的最小值比剩下的所有数都大。要求判无解或构造序列。

差分约束+线段树优化建边。

my record

一些降低编程复杂度的小技巧:

  • 可以把建入树和建出树分开来写,记录一下一棵树的节点数 \(tot\), 然后连树间的边就直接\((cur - tot) ~ ~ ->~ ~ cur\) 就好。

  • 建树的时候可以直接记录一下每个位置对应的叶子节点编号,方便单点查询。

void build_i(int L, int R, int &cur) {
	cur = ++ttot;
	if (L == R)	return i_p[L] = cur, h[cur] = a[L], isg[cur] = a[L] > 0, void();
	int mid = (L + R) >> 1;
	build_i(L, mid, ls[cur]), build_i(mid + 1, R, rs[cur]);
	addedge(cur, ls[cur], 0); addedge(cur, rs[cur], 0);
}

void build_o(int L, int R, int &cur) {
	cur = ++ttot;
	addedge(cur - tcnt, cur, 0);
	if (L == R)	return o_p[L] = cur, h[cur] = a[L], isg[cur] = a[L] > 0, void();
	int mid = (L + R) >> 1;
	build_o(L, mid, ls[cur]), build_o(mid + 1, R, rs[cur]);
	addedge(ls[cur], cur, 0), addedge(rs[cur], cur, 0);
}

P5025 [SNOI2017]炸弹

题意:求一维坐标上炸弹引爆个数

实际上要求的是有向图上的某点出发能经过的点的权值和。

需要单点向区间点连边。因此需要线段树优化建图。

缩点后跑dp 这样会算重,但是洛谷数据太毒了,bzoj这样可以AC,洛谷86pts

由于最终答案一定为一段连续的闭区间,因此可以记录mx和mn,然后dfs+记搜。

然后就56pts了

好像这里建两棵树要WA,看来有的时候建一棵树要更好一些。(只有单点连向多点的时候)

弹跳

这个比较例外。这个题利用K-D Tree空间消耗小的优点,实现单点对矩形的最短路优化建图。

这也提示我们可以灵活运用各种数据结构来优化建图。只要方法合法,且包含所有合法情况,就可以考虑去这么写。

Ants

这个是线段树优化建图跑 2-SAT,需要用到前缀优化建2-SAT的思想。

前缀优化建图通常解决的问题是,一个集合中如果选了其中一个点,那么其余点都不能选。它大概长这个样子:

前缀优化建图 2-sat

其中两排红点为辅助点,分别表示后缀点和前缀点。黑、棕色方点为原始节点。

线段树优化建图的问题则是,一个线段树节点内存着若干个点,如果选了其中一个点,那么其余点都不能选,且线段树的祖先节点内的点和子树内的点也不能被选。它大概长这个样子:

线段树优化建图 2-sat

大概就是把父子的串给连起来。

需要注意的是,这种方法的节点数特别多,点数和边数都是 \(O(n \log n)\) 级别的。这道题是树剖套线段树,所以是 \(O(n \log^2 n)\) 的。

Flags

这个是普通的线段树优化建图跑2-sat,新建的点和新建的边并不符合对称性,只不过与朴素建图等价。

模板:单源最短路(调试用)

void build(int L, int R, int &cur) {
	cur = ++ttot;
	if (L == R)	return ;
	int mid = (L + R) >> 1;
	build(L, mid, ls[cur]), build(mid + 1, R, rs[cur]);
	if (type)	addedge(cur, ls[cur], 0), addedge(cur, rs[cur], 0);
	else	addedge(ls[cur], cur, 0), addedge(rs[cur], cur, 0);
}

int ptot;
void Out(int L, int R, int l, int r, int p, int w, int cur) {
	if (l <= L && R <= r) {
		addedge(cur, p, 0);
		return ;
	}
	int mid = (L + R) >> 1;
	if (l <= mid)	Out(L, mid, l, r, p, w, ls[cur]);
	if (r > mid)	Out(mid + 1, R, l, r, p, w, rs[cur]);
}
void In(int L, int R, int l, int r, int p, int w, int cur) {
	if (l <= L && R <= r) {
		addedge(p, cur, w);
		return ;
	}
	int mid = (L + R) >> 1;
	if (l <= mid)	In(L, mid, l, r, p, w, ls[cur]);
	if (r > mid)	In(mid + 1, R, l, r, p, w, rs[cur]);
}

inline void Link(int l, int r, int l_, int r_, int w) {
	++ptot;
	Out(1, n, l, r, ptot, w, root_o);
	In(1, n, l_, r_, ptot, w, root_i);
}

int find(int L, int R, int pos, int cur) {
	if (L == R)	return cur;
	int mid = (L + R) >> 1;
	if (pos <= mid)	return find(L, mid, pos, ls[cur]);
	return find(mid + 1, R, pos, rs[cur]);
}

struct node{
	int cur;
	ll val;
	bool operator <(const node a) const {
		return val > a.val;
	}
};
priority_queue<node> q;

ll dis[N];
bool vis[N];

inline void dij() {
	int st = find(1, n, s, root_i);
	memset(dis, 0x3f, sizeof(dis));
	
	q.push((node){st, 0}); dis[st] = 0;
	while (!q.empty()) {
		int cur = q.top().cur; q.pop();
		if (vis[cur])	continue;
		vis[cur] = true;
		for (register int i = head[cur]; i; i = e[i].nxt) {
			int to =e[i].to;
			if (dis[to] > dis[cur] + e[i].val) {
				dis[to] = dis[cur] + e[i].val;
				q.push((node){to, dis[to]});
			}
		}
	}
}

void print(int L, int R, int cur) {
	if (L == R) {
		printf("%lld ", dis[cur] >= inf ? -1ll : dis[cur]);
		return ;
	}
	int mid = (L + R) >> 1;
	print(L, mid, ls[cur]);
	print(mid + 1, R, rs[cur]);
}

int main() {
	read(n), read(m), read(s);
	type = 1, build(1, n, root_i); int tot = ttot;
	type = 0, build(1, n, root_o);
	for (register int i = 1; i <= tot; ++i)
		addedge(i, i + tot, 0);
	
	ptot = ttot;
	for (...) Link(l, r, l_, r_);
	dij();
	print(1, n, root_i);
	puts("");
	return 0;
}
posted @ 2021-01-22 21:37  JiaZP  阅读(441)  评论(0编辑  收藏  举报