【XSY2434】【CF787D】遗产(线段树+dijkstra)
\(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;
}