codeforces 787D Legacy (线段树)

题目链接:http://codeforces.com/problemset/problem/787/D

Description

Rick and his co-workers have made a new radioactive formula and a lot of bad guys are after them. So Rick wants to give his legacy to Morty before bad guys catch them.

There are n planets in their universe numbered from 1 to n. Rick is in planet number s (the earth) and he doesn't know where Morty is. As we all know, Rick owns a portal gun. With this gun he can open one-way portal from a planet he is in to any other planet (including that planet). But there are limits on this gun because he's still using its free trial.

By default he can not open any portal by this gun. There are q plans in the website that sells these guns. Every time you purchase a plan you can only use it once but you can purchase it again if you want to use it more.

Plans on the website have three types:

  1. With a plan of this type you can open a portal from planet v to planet u.
  2. With a plan of this type you can open a portal from planet v to any planet with index in range [l, r].
  3. With a plan of this type you can open a portal from any planet with index in range [l, r] to planet v.

Rick doesn't known where Morty is, but Unity is going to inform him and he wants to be prepared for when he finds and start his journey immediately. So for each planet (including earth itself) he wants to know the minimum amount of money he needs to get from earth to that planet.

Input

The first line of input contains three integers nq and s (1 ≤ n, q ≤ 105, 1 ≤ s ≤ n) — number of planets, number of plans and index of earth respectively.

The next q lines contain the plans. Each line starts with a number t, type of that plan (1 ≤ t ≤ 3). If t = 1 then it is followed by three integers vu and w where wis the cost of that plan (1 ≤ v, u ≤ n, 1 ≤ w ≤ 109). Otherwise it is followed by four integers vlr and w where w is the cost of that plan (1 ≤ v ≤ n, 1 ≤ l ≤ r ≤ n, 1 ≤ w ≤ 109).

Output

In the first and only line of output print n integers separated by spaces. i-th of them should be minimum money to get from earth to i-th planet, or  - 1 if it's impossible to get to that planet.

Examples

Input

3 5 1
2 3 2 3 17
2 3 2 2 16
2 2 2 3 3
3 3 1 1 12
1 3 3 17

Output

0 28 12 

Input

4 3 1
3 4 1 3 12
2 2 3 4 10
1 2 4 16

Output

0 -1 -1 12 

题目分析

看完题目,觉得是一个最短路问题,但是想到一种情况:如果每次操作让一个点连接最大的区间,那么建图的时间将用时 O(n*q), 这样是不行的,但是我们可以用线段树来优化建图过程。

线段树的特点在于区间操作,比如求区间最值以及对区间进行整体操作等等,为此我们将所有的顶点构成区间,建立线段树: [1.n] , [1, (n+1)/2 ] , [ (n + 1 )/2 + 1, n ] .....由于我们将某一段连续的点当作线段树上的一个顶点,这样我们在建立 点到区间的边 和 区间到点的边的时候,就不用进行点和点之间的直接建边了,而是在线段树上进行顶点和顶点之间的建边了,即直接建立区间和点之间的边,这将节省建图的时间。

而以区间 [1,n] 建立线段树,自然会想到线段树的叶子结点都是星球(点),那么为了体现从点x到区间建边的操作,我们将线段树所有的顶点当作求最短路时的顶点,也就是说,我们也会把一个区间看作一个顶点,求起点到各个顶点的最短距离,  图如下所示

  

首先,因为我们需要建立从点x到区间[a,b]的边,那么我们必须使得这个区间[a,b]代表的顶点到其子结点的距离为0,这样一来,我们在求最短路的时候,点x代表的顶点到区间[a,b]代表的顶点之间的距离是dis,同时点x到区间代表的点 a, a+1,a+2 ...b 的距离也会是dis,这样也就实现了点x到区间[a,b]中的每一个点的建边,由此我们需要建图如下(图中的蓝线代表距离为0的边):

然后,我们需要建立从区间[a,b]到点x的边,显然,上面的图中区间到点的距离都是0,明显不符合我们的要求,我们需要构建一个新的图。

我们必须使得点a,a+1...b代表的顶点到其父结点的距离为0,这样一来,我们在求最短路的时候,从区间[a,b]代表的顶点到点x代表的顶点之间的距离是dis,同时区间代表的点a, a+1,a+2 ...b 到点 x的距离也是dis,这样也就实现了区间[a,b]中的每一个点到点x的建边,由此我们需要建图如下(图中的蓝线代表距离为0的边):

此时,我们构建出了两个图,第一个用于表示建立从点x到区间[a,b]的边的时候的区间[a,b],第二个图表示建立从区间[a,b]到点x的边的时候的区间[a,b],而我们发现,建边的时候的点x无论在哪个图都会产生同样的效果,因此两个图中的叶子结点是同一个点,为了体现这一效果,我们可以将上述两个图合并为如下的图(图中的蓝线代表距离为0的边):

这样一来,我们就可以正常地为点到区间,区间到点建边了,为了方便,我们将3种建边操作描述如下:

Type == 1 ,建立点u到点v之间的边,权值为w,由于起点和终点都是点,那么由橙色的叶子结点[u,u]向绿色的叶子结点[v,v]建一条权值为w的单向边。

Type == 2,建立区间[a,b]到点u之间的边,权值为w,这个时候,我们由橙色的结点[a,b]向绿色的叶子结点[u.u]建一条权值为w的单向边。

Type == 3,建立点u到区间[a,b]之间的边,权值为w,这个时候,我们由橙色的叶子结点[u.u]向绿色的顶点[a,b]建一条权值为w的单向边。

嗯,方便起见,始终以下线段树为起点,以上线段树为终点,进行建边

下面给出第二组样例构建的图,帮助理解

最后,我们从起点1对整个图跑一遍dijkstra,得到各个叶子结点到起点1的距离:0,-1,-1,12

代码区

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#include<string>
#include<fstream>
#include<vector>
#define bug cout << "**********" << endl
//#define LOCAL = 1;
using namespace std;
typedef long long ll;
const ll inf = 1e18 + 5;
const int mod = 1e8;
const int Max = 1e5 + 10;

struct Node
{
	int l, r;
	int num;
}node1[Max << 3], node2[Max << 3];		//记录上下两个线段树

struct Edge
{
	int to;
	ll dis;
};

vector<Edge>edge[Max << 3];				//记录边的信息,这里用vector存图导致了运行时间较长,请见谅
int pos1[Max << 3], pos2[Max << 3];		//记录上下两个树中的每个叶子结点在线段树中的下标
int n, q, s;
int tot;


void build_up(int l, int r, int num)	//自底向上建边,下线段树
{
	node1[num].l = l;
	node1[num].r = r;
	node1[num].num = ++tot;				//每一个顶点都有唯一的编号,便于建边
	if (l == r)
	{
		pos1[l] = node1[num].num;		//记录下线段树的叶子结点对应的编号,借助pos1直接找到叶子结点
		return;
	}
	int mid = (l + r) >> 1;
	build_up(l, mid, num << 1);
	build_up(mid + 1, r, num << 1 | 1);

	edge[node1[num << 1].num].push_back({ node1[num].num, 0 });
	edge[node1[num << 1 | 1].num].push_back({ node1[num].num, 0 });	//子结点向父结点建边
}

void build_down(int l, int r, int num)	//自顶向下建边
{
	node2[num].l = l;
	node2[num].r = r;
	node2[num].num = ++tot;
	if (l == r)
	{
		pos2[l] = node2[num].num;							//记录上线段树的叶子结点对应的编号,借助pos1直接找到叶子结点
		edge[node2[num].num].push_back({ pos1[l], 0 });		//由上线段树的叶子结点向下线段树的叶子结点建边
		return;
	}
	int mid = (l + r) >> 1;
	build_down(l, mid, num << 1);
	build_down(mid + 1, r, num << 1 | 1);
	edge[node2[num].num].push_back({ node2[num << 1].num, 0 });
	edge[node2[num].num].push_back({ node2[num << 1 | 1].num, 0 });	//父结点向子结点建边
}

//由区间向点建边
void upData_section_to_dot(int l, int r, int v, ll dis, int num)
{
	if (l <= node1[num].l && node1[num].r <= r)
	{
		edge[node1[num].num].push_back({ pos2[v], dis });	//由下线段树的区间向上线段树的叶子结点建边
		return;
	}
	int mid = (node1[num].l + node1[num].r) >> 1;
	if (l <= mid)
		upData_section_to_dot(l, r, v, dis, num << 1);
	if (r > mid)
		upData_section_to_dot(l, r, v, dis, num << 1 | 1);
}

//由点向边建边
void upData_dot_to_section(int l, int r, int u, ll dis, int num)
{
	if (l <= node2[num].l && node2[num].r <= r)
	{
		edge[pos1[u]].push_back({ node2[num].num, dis });	//由下线段树的点向上线段树的区间建边
		return;
	}
	int mid = (node2[num].l + node2[num].r) >> 1;
	if (l <= mid)
		upData_dot_to_section(l, r, u, dis, num << 1);
	if (r > mid)
		upData_dot_to_section(l, r, u, dis, num << 1 | 1);
}


bool vis[Max << 3];	//访问状态
ll dist[Max << 3];	//各个点到起点的距离

void dijkstra(int start)
{
	memset(vis, 0, sizeof(vis));	//初始化访问状态
	for (int i = 1;i <= 8 * n;i++)	//初始化各个点到起点的距离
		dist[i] = inf;
	dist[start] = 0;
	priority_queue < pair<ll, int>, vector<pair<ll, int>>, greater<pair<ll, int> > >queues;//以到起点距离单增的优先队列
	queues.push({ 0,start });		//起点入队

	while (!queues.empty())
	{
		int u = queues.top().second;
		queues.pop();
		if (vis[u]) continue;		//重复访问
		vis[u] = true;
		for (int i = 0;i < edge[u].size(); i++)
		{
			int v = edge[u][i].to;
			ll dis = edge[u][i].dis;
			if (vis[v]) continue;	//重复访问
			if (dist[v] > dist[u] + dis)	//缩边
			{
				dist[v] = dist[u] + dis;
				queues.push({ dist[v],v });
			}
		}
	}
	for (int i = 1;i <= n;i++)		//输出结果
	{
		if (dist[pos1[i]] != inf)
			printf("%lld ", dist[pos1[i]]);
		else
			printf("-1 ");
	}
	printf("\n");
}

int main()
{
#ifdef LOCAL
	freopen("input.txt", "r", stdin);
	freopen("output.txt", "w", stdout);	
	//这个是文件进行输入和输出,如果将13行的#define LOCAL = 1;注释掉,就是常规运行了(黑框框里面的那种)
#endif
	while (scanf("%d%d%d", &n, &q, &s) != EOF)
	{
		for (int i = 0;i < Max << 3; i++)	//多组输入的初始化,其实这里不需要多组输入,习惯使然
			edge[i].clear();
		tot = 0;							//控制编号的变量
		build_up(1, n, 1);
		build_down(1, n, 1);				//构建上下线段树
		while (q--)
		{
			int type, u, v, l, r;
			ll cost;
			scanf("%d", &type);
			if (type == 1)
			{
				scanf("%d%d%lld", &u, &v, &cost);
				edge[pos1[u]].push_back({ pos2[v], cost });	//由下线段树的叶子结点向上线段树的叶子结点建边
			}
			else if (type == 2)
			{
				scanf("%d%d%d%lld", &u, &l, &r, &cost);
				upData_dot_to_section(l, r, u, cost, 1);	//由下线段树的叶子结点向上线段树的区间结点建边
			}
			else
			{
				scanf("%d%d%d%lld", &v, &l, &r, &cost);
				upData_section_to_dot(l, r, v, cost, 1);	//由下线段树的区间结点向上线段树的叶子结点建边
			}
		}
		dijkstra(pos1[s]);	//最短路
	}
	return 0;
}

 

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值