Java教程

线段树优化建图

本文主要是介绍线段树优化建图,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

前言

鸣谢 ckj 学长,yyds

引子

挺简单但是很实用的一种建图方法,可以将 \(O(n^2)\) 的建图优化到 \(O(n~log n)\)

先看一道例题 CF786B Legacy

题目大意:有 \(n\) 个点、\(q\) 次操作。每一种操作为以下三种类型中的一种:

  • 操作一:连一条 \(u→v\) 的有向边,权值为 \(w\)
  • 操作二:对于所有 \(i∈[l,r]\) 连一条 \(u→i\) 的有向边,权值为 \(w\)。
  • 操作三:对于所有 \(i∈[l,r]\) 连一条 \(i→u\) 的有向边,权值为 \(w\)。

求从点 \(s\) 到其他点的最短路。\(1≤n,q≤10^5,1≤w≤10^9\)

mind

首先想到的肯定是暴力建图,每一次操作 \(O(n)\) 扫一边,显然会超时

因为是对一个区间进行操作,所以考虑用线段树来搞点事情

建两棵线段树,一棵为"出树"(所有边指向儿子),一棵是 "入树" (所有边指向父亲)

对向区间建边的,可以在线段树上找到这段区间,然后直接指向这段区间就好了

具体操作可以看这 讲的超级详细

学的时候唯一难理解的是实际点的编号是如何和线段树中编号怎么避免冲突的?

其实每个点都对应着线段树中的一个叶子节点,对于每个点的实际标号,可以理解为线段树中是第几个叶子,然后它肯定要对应线段树的一个标号,而我们建图是只利用线段树的编号进行建图,所以每一个点都要对应到线段树上的点才能连边(建图),这样就保证了只会用到线段树的编号,避免了矛盾

code

下面代码有点小错误,洛谷一直 waiting 不给测

应该没有什么大错误 = =

/*
work by:Ariel_
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#define int long long 
#define lson rt << 1
#define rson rt << 1|1
using namespace std;
const int N = 3e6 + 5, K = 5e5 + 5;
int read() {
  int x = 0, f = 1; char c = getchar();
  while(c < '0' || c > '9'){if (c == '-') f = -1;c = getchar();}
  while(c >= '0' && c <= '9') {x = x * 10 + c - '0';c = getchar();}
  return x * f;
}
int n, m, s, opt, a[N]; 
struct edge{int v, nxt, w;}e[N];
int head[N], E;
void add_edge(int u, int v, int w) {
	e[++E] = (edge) {v, head[u], w};
	head[u] = E; 
}
namespace Seg{
   void build(int rt, int l, int r) {
   	  if (l == r) {a[l] = rt; return;}
   	  add_edge(rt, rt << 1, 0), add_edge(rt, rt << 1 | 1, 0);
	  add_edge((rt << 1) + K, rt + K, 0), add_edge((rt << 1 | 1) + K, rt + K, 0);
	  int mid = (l + r) >> 1;
	  build(lson, l, mid), build(rson, mid + 1, r); 
   }
   void modify(int rt, int l, int r, int L, int R, int v, int w) {
   	  if (L <= l && r <= R) {
   	     if (opt == 2) add_edge(v + K, rt, w);	
		 else add_edge(rt + K, v, w);
		 return; 	
	   }
	  int mid = (l + r) >> 1;
	  if (L <= mid) modify(lson, l, mid, L, R, v, w);
	  else if(R > mid) modify(rson, mid + 1, r, L, R, v, w);  
   }
}
using namespace Seg;
priority_queue<pair<int, int> > q;
int dis[N], vis[N];
void dij(int s) {
   memset(dis, 0x3f, sizeof dis);dis[s] = 0;
   q.push(make_pair(0, s));
   while(q.size()) {
   	  int u = q.top().second; q.pop();
   	  if (vis[u]) continue;
   	  vis[u] = 1;
   	  for (int i = head[u]; i; i = e[i].nxt) {
   	  	     int v = e[i].v;
	     if (e[i].w + dis[u] < dis[v]) {
	     	 dis[v] = e[i].w + dis[u];
	     	 q.push(make_pair(-dis[v], v));//默认从小到大排序,所以要取负号 
		 }
	  }
   }
}
signed main(){
  n = read(), m = read(), s = read();
  build(1, 1, n);
  for (int i = 1; i <= n; i++) add_edge(a[i], a[i] + K, 0), add_edge(a[i] + K, a[i], 0);
  for (int i = 1, x, y, z, l, r, w; i <= m; i++) {
  	 opt = read();
  	 if (opt == 1)  {
  	   x = read(), y = read(), z = read();//这里的 x, y 对应线段树的第几个叶子节点,a[x] 存的是线段树中第 x 叶子节点的标号 
	   add_edge(a[x] + K, a[y], z);	
	 }
	 else {
	 	x = read(), l = read(), r = read(), w = read();
		modify(1, 1, n, l, r, a[x], w); 
	 }
  }
  dij(a[s] + K);
  for (int i = 1; i <= n; i++) 
    printf("%lld%c",dis[a[i]]!=0x3f3f3f3f3f3f3f3fll?dis[a[i]]:-1,i==n?'\n':' ');
  puts("");
  return 0;
}

写在后面

关于优化建图的方式还有分块优化建图,KD 树优化建图,主席树连边……

争取退役前干完 QWQ

这篇关于线段树优化建图的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!