(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).
(Input)
第一行输入包括三个正整数(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))
(Output)
输出一行包括(n)个数,(a_{i})表示从地球到(i)这个星球的最小花费,如果到达不了则输出(-1)。
(Sample) (Input)
样例输入1:
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
样例输入2:
4 3 1
3 4 1 3 12
2 2 3 4 10
1 2 4 16
(Sample) (Output)
样例输出1:
0 28 12
样例输出2:
0 -1 -1 12
(Source)
练习题 树4-1-线段树
思路
首先,这道题可以很容易看出是一道最短路问题
我们首先想到就是对于(v)每一个([l,r])区间内的数进行连边
但这样的连边的最坏复杂度都可以达到(O(nq)),极其容易被卡死
所以我们再仔细观察题目,不去考虑最短路的话。。。
一堆区间操作是不是很像线段树?!
于是我们使用线段树优化最短路
我们考虑对于情况2,3分别建一棵线段树,为入树和出树
其中线段树的每一个节点都表示一个区间
对于操作2,我们就可以将图的节点与入树上区间所对应的节点连边
对于操作3,我们就可以将出树上区间所对应的节点与图的节点连边
我们还需要在入树和出树他们各自自己树内建边,当然权值为(0)
最后跑一遍最短路(dijkstra)就可以了
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=700050,M=3500010l;
const ll INF=0x7f7f7f7f7f7f7f7f;
int n,t,s,cnt=0;
int head[N];
int lc1[N],lc2[N],rc1[N],rc2[N];
//lc1[now],rc1[now]分别表示在入树内now的左右儿子,lc2,rc2是表示在出树内
int to[M<<1],nxt[M<<1];
int val[M<<1];
int nodetot=0;//最短路内的节点数
ll dis[N];
bool vis[N];
struct edge
{
int u;
ll cost;
bool operator < (const edge & e)const
{
return cost>e.cost;
}
}now;
priority_queue<edge>q;
void add(int u,int v,int w)
{
to[++cnt]=v;
nxt[cnt]=head[u];
val[cnt]=w;
head[u]=cnt;
}
int build1(int l,int r)
{
if(l==r)return l;
int now=++nodetot;
int mid=(l+r)>>1;
lc1[now-n]=build1(l,mid);
add(now,lc1[now-n],0);//父亲与儿子连入边,父亲->儿子
rc1[now-n]=build1(mid+1,r);
add(now,rc1[now-n],0);
return now;
}
int build2(int l,int r)
{
if(l==r)return l;
int now=++nodetot;
int mid=(l+r)>>1;
lc2[now-n]=build2(l,mid);
add(lc2[now-n],now,0);//父亲与儿子连出边,儿子->父亲
rc2[now-n]=build2(mid+1,r);
add(rc2[now-n],now,0);
return now;
}
void modify1(int v,int l,int r,int w,int ql,int qr,int k)
{
if(l<=ql&&qr<=r)
{
add(v,k,w);//到达区间内,将图内的点与这个区间对应的节点连边
return ;
}
int mid=(ql+qr)>>1;
if(l<=mid)modify1(v,l,r,w,ql,mid,lc1[k-n]);
if(r>mid)modify1(v,l,r,w,mid+1,qr,rc1[k-n]);
}
void modify2(int v,int l,int r,int w,int ql,int qr,int k)
{
if(l<=ql&&qr<=r)
{
add(k,v,w);
return ;
}
int mid=(ql+qr)>>1;
if(l<=mid)modify2(v,l,r,w,ql,mid,lc2[k-n]);
if(r>mid)modify2(v,l,r,w,mid+1,qr,rc2[k-n]);
}
void dijkstra(int st)
{
dis[st]=0;
q.push((edge){st,0});
while(!q.empty())
{
now=q.top();
q.pop();
if(vis[now.u])continue;
vis[now.u]=1;
dis[now.u]=now.cost;
for(int i=head[now.u];i;i=nxt[i])
{
if(!vis[to[i]]&&(now.cost+(ll)val[i])<dis[to[i]])
{
q.push((edge){to[i],now.cost+(ll)val[i]});
}
}
}
}
int main()
{
scanf("%d %d %d",&n,&t,&s);
memset(dis,0x7f,sizeof(dis));
nodetot=n;
int rt1=build1(1,n);
int rt2=build2(1,n);
int op,v,u,l,r,w;
for(int i=1;i<=t;i++)
{
scanf("%d",&op);
if(op==1)
{
scanf("%d %d %d",&v,&u,&w);
add(v,u,w);//直接连边
}
if(op==2)
{
scanf("%d %d %d %d",&v,&l,&r,&w);
modify1(v,l,r,w,1,n,rt1);
}
if(op==3)
{
scanf("%d %d %d %d",&v,&l,&r,&w);
modify2(v,l,r,w,1,n,rt2);
}
}
dijkstra(s);
for(int i=1;i<=n;i++)
{
if(dis[i]==INF)printf("-1 ");
else printf("%lld ",dis[i]);
}
return 0;
}