读题发现,题目里有三种连边方式:
1.两点之间连一条有向边。
2.一个点与给定区间中的点连一条有向边。
3.给定区间中的点和一个点连一条有向边。
剩下的就是跑最短路。
两点之间连边很好处理,但是如何在区间之间连边?按平时链式前向星加边,最坏可能是n方的复杂度,建个边都要死。
这时就有线段树来优化一波。看到区间,还是可以往线段树的方向上去想一想。因为线段树的每个点都代表一段连续的区间。
可以建两棵线段树,一棵维护区间向外边的点连边,另一棵维护外边的点与区间中连边。用动态开点,保存左儿子和右儿子。
递归到叶子节点时,叶子节点的编号就是该点。所以一开始的根节点是n。最后回溯时将每个点与左右儿子都连上一条边权为0的边,这样连上某个线段树上的点就等同于某个点与区间中的点都连了一条边,时间复杂度nlog(n+m)(m次询问)。
按询问连边时就是线段树update操作改一下,最后跑最短路即可,注意要开long long
代码如下:
//思路是真的非常巧妙!! #include<bits/stdc++.h> using namespace std; const int maxn=1e6+7; const long long INF=0x3f3f3f3f3f3f3f3f; struct node{ int nxt,to,val; }edge[maxn*20]; priority_queue< pair<long long,int> >q; int head[maxn],cnt; bool vis[maxn]; int n,m,s; int opt,u,v,w; void add(int x,int y,int v){ edge[++cnt].nxt=head[x]; edge[cnt].to=y; edge[cnt].val=v; head[x]=cnt; } int num,rt1,rt2; int lc[maxn],rc[maxn]; void build1(int &rt,int l,int r){ if(l==r){ rt=l; return; } rt=++num; int mid=(l+r)>>1; build1(lc[rt],l,mid); build1(rc[rt],mid+1,r); add(rt,lc[rt],0); add(rt,rc[rt],0); } void build2(int &rt,int l,int r){ if(l==r){ rt=l; return; } rt=++num; int mid=(l+r)>>1; build2(lc[rt],l,mid); build2(rc[rt],mid+1,r); add(lc[rt],rt,0); add(rc[rt],rt,0); } int l1,r1; void update(int rt,int l,int r,int u,int v,int which){ if(l1<=l&&r<=r1){ which==2?add(u,rt,v):add(rt,u,v);//包含了当前区间,直接连边。 return; } int mid=(l+r)>>1; if(l1<=mid) update(lc[rt],l,mid,u,v,which); if(r1>mid) update(rc[rt],mid+1,r,u,v,which); } long long dis[maxn]; void dijkstra(int x){ for(int i=1;i<=num;i++) dis[i]=INF; memset(vis,false,sizeof(vis)); q.push(make_pair(0,x)); dis[x]=0; while(q.size()!=0){ int u=q.top().second; q.pop(); if(vis[u]) continue; vis[u]=true; for(int i=head[u];i;i=edge[i].nxt){ int v=edge[i].to; if(dis[v]>dis[u]+edge[i].val){ dis[v]=dis[u]+edge[i].val; q.push(make_pair(-dis[v],v)); } } } } int main(){ scanf("%d%d%d",&n,&m,&s); num=n; build1(rt1,1,n); build2(rt2,1,n); for(int i=1;i<=m;i++){ scanf("%d",&opt); if(opt==1){ scanf("%d%d%d",&u,&v,&w); add(u,v,w); } else{ scanf("%d%d%d%d",&u,&l1,&r1,&w); update(opt==2?rt1:rt2,1,n,u,w,opt); } } dijkstra(s); for(int i=1;i<=n;i++){ printf("%lld ",dis[i]<INF?dis[i]:-1); } return 0; }