• 数据结构优化建图


    数据结构优化建图

    有的时候我们需要将编号在 ([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;
    }
    
  • 相关阅读:
    JS 信息提示弹框封装
    JS 功能弹框封装
    css3 弹框提示样式
    css3 弹框功能样式
    vscode使用Markdown文档编写
    .NET程序员提高效率的70多个开发工具
    Postman 使用方法详解
    【算法】从一组数中找出和为指定值的任意组合
    .NET Core的依赖注入
    .Net IOC框架入门之——Autofac
  • 原文地址:https://www.cnblogs.com/JiaZP/p/14315538.html
Copyright © 2020-2023  润新知