线段树优化建图

给定n个点,有三种操作:
1.连一条从x到y权值为w的边。
2.连一条从[l,r]到y权值为w的边。
3.连一条y到[l,r]权值为w的边。
给定一个起点,求单源最短路。

/*
线段树优化建图
对于点到区间上所有点都加上一条路径这样的操作
可以考虑线段树来优化建图
将区间考虑成点,那么点到区间的边就变成为点到一些点的边(这些点不会超过log次)
线段树的叶子节点就是真实的点
线段树每个节点又需要拆为两个点,一个点可以到达它的所有儿子,一个点可以被它的所有儿子到达
从这个区间出去一条路径(这个是从其儿子节点跑出去的),从一个点进入这个区间(节点进去这个区间跑到儿子节点去) 
*/ 
#include <iostream>
#include <vector>
#include <queue>
using namespace std;

typedef long long ll;
const int maxn = 1e5+5;
struct node{    //线段树节点 
	int l,r;
	int id1,id2;
}a[maxn*4];
int cnt = 0;    //每个线段树节点对应了两个点的编号 
struct nodex{
	int id;
	ll val;
	nodex(int a,ll b)
	{
		id = a;
		val = b;
	}
	bool operator<(const nodex&n)const
	{
		return val > n.val;
	}
};
int p[maxn];   //i这个点被映射成了哪个点 
vector<nodex> g[8*maxn];   //记得开8倍 
ll dist[maxn*8],vis[maxn*8]; 

void update(int x)  //线段树对应到图上的结构 
{
	a[x].id1 = ++cnt;
	g[a[x].id1].push_back(nodex(a[2*x].id1,0));    //id1可以到达任意的儿子节点 
	g[a[x].id1].push_back(nodex(a[2*x+1].id1,0));
	a[x].id2 = ++cnt;
	g[a[2*x].id2].push_back(nodex(a[x].id2,0));    //id2被任意儿子节点到达 
	g[a[2*x+1].id2].push_back(nodex(a[x].id2,0)); 
}
void build(int x,int l,int r)    //建树 
{
	a[x].l = l,a[x].r = r;
	if( l == r )
	{
		a[x].id1 = ++cnt;
		a[x].id2 = a[x].id1;
		p[l] = cnt;
		return;
	}
	int m = (l+r)>>1;
	build(2*x,l,m);build(2*x+1,m+1,r);
	update(x);
}
void add(int x,int l,int r,int p,ll w,int dir)   //dir表示从p到[l,r]还是[l,r]到p 
{
	if( a[x].l == l && a[x].r == r )
	{
		if( dir == 0 ) g[a[x].id2].push_back(nodex(p,w));  //dir==0表示从[l,r]连向p,这里需要用id2 
		else g[p].push_back(nodex(a[x].id1,w));   //否则用id1 
		return;  
	}
	int m = (a[x].l+a[x].r)>>1;
	if( r <= m ) add(2*x,l,r,p,w,dir);
	else if( l > m ) add(2*x+1,l,r,p,w,dir);
	else
	{
		add(2*x,l,m,p,w,dir);
		add(2*x+1,m+1,r,p,w,dir);
	}
}
void dij(int begin)
{
	for (int i = 1; i <= cnt; i++)
	{
		dist[i] = 1e18;
		vis[i] = 0;
	}
	priority_queue<nodex> q;
	dist[begin] = 0;
	q.push(nodex(begin,0));
	while( !q.empty() )
	{
		int x = q.top().id;
		q.pop();
		if( vis[x] ) continue;
		vis[x] = 1;
		for (int i = 0; i < g[x].size(); i++)
		{
			int t = g[x][i].id;
			if( dist[t] > dist[x] + g[x][i].val )
			{
				dist[t] = dist[x] + g[x][i].val;
				q.push(nodex(t,dist[t]));
			}
		}
	}
} 

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	int n,q,s;
	cin >> n >> q >> s;
	build(1,1,n);
	while( q-- )
	{
		int op;
		cin >> op;
		if( op == 1 )
		{
			int x,y,v;
			cin >> x >> y >> v;
			add(1,x,x,p[y],v,0);
		}else
		{
			int v,l,r,w;
			cin >> v >> l >> r >> w;
			if( op == 2 ) add(1,l,r,p[v],w,1);
			else add(1,l,r,p[v],w,0);
		}
	}
	dij(p[s]);
	for (int i = 1; i <= n; i++)
	{
		if( dist[p[i]] == 1e18 ) cout << -1;
		else cout << dist[p[i]];
		if( i == n ) cout << '\n';
		else cout << ' ';
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值