首页 > 其他分享 >3/28 线段树优化建图

3/28 线段树优化建图

时间:2024-03-28 16:58:49浏览次数:31  
标签:int 28 线段 tr add seg 建图 flg dis

(1) CF786B Legacy

  • 有一张 \(n\) 个节点和若干条边。边用 \(q\) 条信息表示:

    • 1 v u w 表示有一条连接 \(v \to u\) 的有向边,边权为 \(w\);
    • 2 v l r w 表示对于所有 \(u \in [l, r]\),都有一条连接 \(v \to u\) 的有向边,边权为 \(w\);
    • 3 v l r w 表示对于所有 \(u \in [l, r]\),都有一条连接 \(u \to v\) 的有向边,边权为 \(w\)。

    求 \(s\) 到每个点的最短路。


线段树优化建图。

建两颗线段树“入树”和“出树”,每次连边时将入树的线段树节点连向出树的线段树节点。

同时,在入树中,我们连边儿子 \(\to\) 父亲边权为 \(0\)。在出树中连边父亲 \(\to\) 儿子边权为 \(0\)。

又因为两个数中每个叶子节点本质是一样的,所以在相同的叶子节点间连接边权为 \(0\) 的双向边。

最后从某棵树的叶子节点 \(s\) 跑最短路即可。

$\color{blue}\text{Code}$
#include <bits/stdc++.h>

using namespace std;

#define int long long

const int N = 1000010;

int n, q, s;
vector<pair<int, int> > g[N];
int root[2];

void add(int a, int b, int w, bool flg = 0) {
	g[a].push_back({b, w});
	if (flg) add(b, a, w);
	return;
}

struct Tree {
	int l, r;		// 区间
	int ls, rs;	// 左右儿子 
	bool flg;		// 哪棵树 
}tr[N];

int idx;
map<pair<int, bool>, int> Id;

int build(int l, int r, bool flg) {
	int u = ++ idx;
	tr[u].l = l, tr[u].r = r, tr[u].flg = flg;
	if (l != r) {
		int mid = l + r >> 1;
		tr[u].ls = build(l, mid, flg);
		tr[u].rs = build(mid + 1, r, flg);
		if (!flg) add(tr[u].ls, u, 0), add(tr[u].rs, u, 0);
		else add(u, tr[u].ls, 0), add(u, tr[u].rs, 0); 
	}
	else Id[{l, flg}] = u;
	return u;
}

int dis[N];
bool st[N];

void Dijkstra(int s) {
	priority_queue<pair<int, int>, vector<pair<int, int> >, greater<pair<int, int> > > q;
	q.push({0, s});
	memset(dis, 0x3f, sizeof dis);
	dis[s] = 0;
	while (q.size()) {
		int u = q.top().second;
		q.pop();
		if (st[u]) continue;
		st[u] = true;
		for (auto t : g[u]) {
			int v = t.first, w = t.second;
			if (dis[v] > dis[u] + w) {
				dis[v] = dis[u] + w;
				q.push({dis[v], v});
			}
		}
	}
	return;
}

void u_to_seg(int u, int l, int r, int to, int w) {
	if (tr[u].l >= l && tr[u].r <= r) add(to, u, w);
	else {
		int mid = tr[u].l + tr[u].r >> 1;
		if (l <= mid) u_to_seg(tr[u].ls, l, r, to, w);
		if (r > mid)  u_to_seg(tr[u].rs, l, r, to, w);
	}
	return;
}

void seg_to_v(int u, int l, int r, int to, int w) {
	if (tr[u].l >= l && tr[u].r <= r) add(u, to, w);
	else {
		int mid = tr[u].l + tr[u].r >> 1;
		if (l <= mid) seg_to_v(tr[u].ls, l, r, to, w);
		if (r > mid)  seg_to_v(tr[u].rs, l, r, to, w);
	}
	return;
}

signed main() {
	cin >> n >> q >> s;
	
	root[0] = build(1, n, 0);
	root[1] = build(1, n, 1);
	for (int i = 1; i <= n; ++ i ) {
		add(Id[{i, 0}], Id[{i, 1}], 0, 1);
	}
	
	while (q -- ) {
		int op, v, u, l, r, w;
		cin >> op;
		if (op == 1) {
			cin >> v >> u >> w;
			add(Id[{v, 0}], Id[{u, 1}], w);
		}
		else if (op == 2) {
			cin >> u >> l >> r >> w;
			u_to_seg(root[1], l, r, Id[{u, 0}], w);
		}
		else {
			cin >> v >> l >> r >> w;
			seg_to_v(root[0], l, r, Id[{v, 1}], w);
		}
	}
	
	Dijkstra(Id[{s, 0}]);
	for (int i = 1; i <= n; ++ i ) {
		int res = dis[Id[{i, 0}]];
		if (res > 1e15) res = -1;
		cout << res << ' ';
	}
	
	return 0;
}

(2) P6348 Journeys

  • 有一张 \(n\) 个节点和若干条边。边用 \(m\) 条信息表示:

    • l1 r1 l2 r2 表示对于所有 \(u \in [l1, r1], v \in [l2, r2]\),都有一条双向边连接 \(u, v\)。

    求 \(P\) 到每个点的最短路。


与上题类似。对于每条信息 l1 r1 l2 r2,我们建立一个虚点,并将 \(l1 \sim r_1\) 和 \(l2 \sim r2\) 向这个虚点连接边权为 \(\frac 12\) 的双向边。这里仍然是线段树优化。

然后跑最短路即可。

实际上,在向虚点连边时可以连接边权为 \(1\) 的边,最终答案全部除以二。

$\color{blue}\text{Code}$
#include <bits/stdc++.h>

using namespace std;

const int N = 5000010;

int n, q, s;
vector<pair<int, double> > g[N];
int root[2];

void add(int a, int b, double w) {
	g[a].push_back({b, w});
	return;
}

struct Tree {
	int l, r;		// 区间
	int ls, rs;	// 左右儿子 
	bool flg;		// 哪棵树 
}tr[N];

int idx;
map<pair<int, bool>, int> Id;

int build(int l, int r, bool flg) {
	int u = ++ idx;
	tr[u].l = l, tr[u].r = r, tr[u].flg = flg;
	if (l != r) {
		int mid = l + r >> 1;
		tr[u].ls = build(l, mid, flg);
		tr[u].rs = build(mid + 1, r, flg);
		if (!flg) add(tr[u].ls, u, 0), add(tr[u].rs, u, 0);
		else add(u, tr[u].ls, 0), add(u, tr[u].rs, 0); 
	}
	else Id[{l, flg}] = u;
	return u;
}

double dis[N];
bool st[N];

void Dijkstra(int s) {
	priority_queue<pair<double, int>, vector<pair<double, int> >, greater<pair<double, int> > > q;
	q.push({0, s});
	for (int i = 1; i <= idx; ++ i ) dis[i] = 1e18; 
	dis[s] = 0;
	while (q.size()) {
		int u = q.top().second;
		q.pop();
		if (st[u]) continue;
		st[u] = true;
		for (auto t : g[u]) {
			int v = t.first;
			double w = t.second;
			if (dis[v] > dis[u] + w) {
				dis[v] = dis[u] + w;
				q.push({dis[v], v});
			}
		}
	}
	return;
}

void u_to_seg(int u, int l, int r, int to, double w) {
	if (tr[u].l >= l && tr[u].r <= r) add(to, u, w);
	else {
		int mid = tr[u].l + tr[u].r >> 1;
		if (l <= mid) u_to_seg(tr[u].ls, l, r, to, w);
		if (r > mid)  u_to_seg(tr[u].rs, l, r, to, w);
	}
	return;
}

void seg_to_v(int u, int l, int r, int to, double w) {
	if (tr[u].l >= l && tr[u].r <= r) add(u, to, w);
	else {
		int mid = tr[u].l + tr[u].r >> 1;
		if (l <= mid) seg_to_v(tr[u].ls, l, r, to, w);
		if (r > mid)  seg_to_v(tr[u].rs, l, r, to, w);
	}
	return;
}

signed main() {
	cin >> n >> q >> s;
	
	root[0] = build(1, n, 0);
	root[1] = build(1, n, 1);
	for (int i = 1; i <= n; ++ i ) {
		add(Id[{i, 0}], Id[{i, 1}], 0);
		add(Id[{i, 1}], Id[{i, 0}], 0);
	}
	
	while (q -- ) {
		int l1, r1, l2, r2;
		cin >> l1 >> r1 >> l2 >> r2;
		
		seg_to_v(root[0], l1, r1, ++ idx, 0.5);
		u_to_seg(root[1], l2, r2, idx, 0.5);
		seg_to_v(root[0], l2, r2, ++ idx, 0.5);
		u_to_seg(root[1], l1, r1, idx, 0.5);
	}
	
	Dijkstra(Id[{s, 0}]);
	for (int i = 1; i <= n; ++ i ) {
		cout << dis[Id[{i, 0}]] << '\n';
	}
	
	return 0;
}

标签:int,28,线段,tr,add,seg,建图,flg,dis
From: https://www.cnblogs.com/2huk/p/18102074

相关文章

  • 2024.03.28【UI设计】新拟态风格设计
    新拟态风格就是类似于给图形图案制作出3D的凸出或者凹进效果的风格这个风格的设计需要使用到即时设计软件的蒙版(与ai不同,ai的蒙版仅有透明度蒙版,无轮廓蒙版)新拟态风格的实现主要是通过三个效果:(1)一个相对浅色高斯模糊效果元素、(2)一个相对深色的无效果元素、(3)一个正常颜色的高......
  • P2887 [USACO07NOV] Sunscreen G
    原题链接题解1.题目可以抽象转化为,若干个点和线段,求问最多有多少点和线段能配对,如果点在线段内2.我们可以采用贪心的方法,对点升序排序,对线段按右端点升序排序为什么请看下图code#include<bits/stdc++.h>usingnamespacestd;structnode{intl,r,chose;}cow[2505]......
  • 3.25~3.28
    另:?咋写这玩意的时候突然耳鸣了几秒我不会要趋势了吧(我发现和5k聊题总会出点问题倒不是说听不懂他的思路而是出在一些奇奇怪怪的地方......
  • 写模板,线段树
    1意义:线段是是为了对区间中的元素进行操作,而衍生出来的一种数据结构,比如区间加减,区间求和。线段树将1~n的区间分解成4n个小区间。2过程:区间修改就是对一个或者多个节点按照设定的规则对数值进行修改。区间查询就是对一个或多个节点查询的结果按规则进行合并,得到最终结果。其......
  • 算法小笔记0328
    1ios::sync_with_stdio(0);ios::sync_with_stdio(false);是C++中用于关闭C++输入输出流(iostream)与C输入输出库(stdio)同步的语句。默认情况下,C++的流库与C的stdio库是同步的,这意味着你可以混用cin,cout和scanf,printf等而不会出现问题。但是这种同步会导致性能下......
  • 算法打卡day28|贪心算法篇02|Leetcode 122.买卖股票的最佳时机 II、55. 跳跃游戏、45.
    算法题Leetcode122.买卖股票的最佳时机II题目链接:122.买卖股票的最佳时机II 大佬视频讲解:买卖股票的最佳时机II视频讲解 个人思路因为只有一只股票,且两天作一个交易单元,那每次只收集正利润就可以最终最多可以获取的利润,可以用贪心。解法贪心法从下图可以发......
  • CAXA装配图中单击序号,有图开图,无图建图
    1//点序号,有图开图,无图建图2voidcmdOpenDrawFromSN()3{4//单击一个序号,炸开,找数字,成功后删除炸开的实体组5crx_nameen;6crx_pointpt;7CRxGePoint3dpB;//单行文字的基点8CRxGePoint3dpS;//选择实体时的单击点9......
  • 24/3/27 线段树
    (1)P4145上帝造题的七分钟2/花神游历各国水。$\color{blue}\text{Code}$#include<iostream>#include<cmath>usingnamespacestd;#defineintlonglongconstintN=1000010;intn,m,a[N],k,l,r;#definels(u<<1)#definers(u<<......
  • L2-028 秀恩爱分得快
    可恶的模拟,借鉴了别人的思路,这样写很清晰#include<bits/stdc++.h>usingnamespacestd;usingll=longlong;constintN=1e5+10;constintinf=0x3f3f3f3f;constintmod=1e9+7;intn,q,m;vector<int>a,b;doubleg[1010][1010];doublemaxn[1010];set<......
  • 统计将重叠区间合并成组的方案数.18098728
    统计将重叠区间合并成组的方案数给你一个二维整数数组ranges,其中ranges[i]=[starti,endi]表示starti到endi之间(包括二者)的所有整数都包含在第i个区间中。你需要将ranges分成两个组(可以为空),满足:每个区间只属于一个组。两个有交集的区间必须在同一个组内......