Description
【题目描述】
Rick和他的同事们研究出了一种新的有关放射的公式,于是许多坏人就在追赶他们。所以Rick希望在被坏人抓住之前把遗产给Morty。
在他们的宇宙里总共有n颗行星,每颗行星有它自己的编号(编号为1到n)。Rick所在的行星的编号是s(地球),但是他不知道Morty在哪?总所周知,Rick有一门能打开奇妙入口的枪。在这把枪的帮助下,他能打开一扇单向门去往任意一个星球(包括那把枪自己所在的星球),但是这玩意是有限制的,因为Rick用的是这玩意的免费试用版。
一般而言,他不能用这把枪打开任意一扇单向的门。但是有q个套餐在它的官网上售卖。每一次你购买了这个套餐,你就能也仅仅能使用它一次,但是你可以重复购买(如果你觉得需要多次使用的话)。
网站上的套餐有以下三种类型:
1.打开一扇从v到u的门
2.打开一扇从v到[l,r]之间任何一个的门
3.打开一扇从[l,r]到v之间任何一个的门
Rick不知道Morty在哪?但是Unity准备告诉他。于是Rick就要准备好一切。
因为Rick的预算不多,所以他想知道从他的星球出发,到达每一个星球的最少花费是多少,如果到达不了,就输出-1.
【输入描述】
第一行输入包括三个正整数n,q,s(1<=n,q<=10^5,1<=s<=n),n表示行星的个数,q表示计划的总数,s表示地球的编号。
接下来q行每行包括一个计划。每一行的第一个数字为t(1<=t<=3),表示该计划的类型。如果t=1就跟着输出v,u和w,w表示这个计划的花费。其他的就输入v,l,r和w。 (1<=v,u<=n,1<=l<=r<=n,1<=w<=10^9)
【输出描述】:
输出一行包括n个数,ai表示从地球到i这个星球的最小花费,如果到达不了则输出-1。
这题中提到了区间二字,我们自然而然地想到了用线段树来建图,再在图上跑一个最短路就可以了。
那我们应该怎么来建图呢?
首先,(1)的我们只用将单点连边就好了。
那我们怎么处理区间的连边呢?
想象一下,我们用一个线段树来保存区间的连边,比如说,在线段树上
(1-2)就代表1至2这个区间。
但是我们直接在一棵线段树上连边根本不行,入边和出边混在一起,我们以不能分清那一条边是树边还是新添加的边。
所以我们开两棵线段树,分别维护入边和出边。
维护入边的线段树中的全部边朝下,维护出边的线段树中的全部边朝上。
我们再按照题目要求建边,最后再在建好的图上面跑一个dijkstra就好了。
维护入边
void addein(int hao,int l,int r,int L,int R,int x,int cost)
{
if(L<=l&&R>=r)
{
adde(x,in[hao],cost);
return;
}
int mid=(l+r)>>1;
if(L<=mid)
{
addein(hao<<1,l,mid,L,R,x,cost);
}
if(R>mid)
{
addein(hao<<1|1,mid+1,r,L,R,x,cost);
}
}
维护出边
void addeout(int hao,int l,int r,int L,int R,int x,int cost)
{
if(L<=l&&R>=r)
{
adde(out[hao],x,cost);
return;
}
int mid=(l+r)>>1;
if(L<=mid)
{
addeout(hao<<1,l,mid,L,R,x,cost);
}
if(R>mid)
{
addeout(hao<<1|1,mid+1,r,L,R,x,cost);
}
}
另:cnt一开始要设为 n n n,因为原本就有 n n n个单独的点。