这不裸的dij吗?来,弄他。
打完以后发现不妙,这数据范围略神奇……算一算,考一场都可能跑不出来。map去重边(成功额外引入log)不怕,交。TLE,54。
这不玩呢吗,把map去了,交。MLE,71……这题考场性价比可能挺高的。
尝试改成vector,没省内存,反而更慢了。
发现以前没学过的一个神奇知识点——线段树优化建图。
这东西,一般出现某个区间和另一个区间内的若干元素建边时使用,因为一个点一个点的建肯定Boom。
这时,根据我们对线段树的理解,我们可以把线段树上的节点当成图中的节点,跟据线段树上一个节点可以表示一个区间这个性质就可以加快建边了,剩下的就是跑一个dij的事。
具体一点,过程如下(你看完这段话肯能还得看看别的博客):
1.建立一颗线段树,叫做“出树”,代表离开一个节点的信息,出树的所有儿子建边指向父亲,边权为零,举个栗子,可以理解为,从1出去,也是从[1,2]中出去,也是从[1,4]中出去,同时没有额外的花费。
2.建立另一颗线段树,叫做“入树”,代表进入一个节点的信息,入树的所有父亲建边指向儿子,边权为零,举个核桃,可以理解为,进入1,也是进入[1,2],也是进入[1,4],同时没有花费。(当然你也可以不这么理解,因为容易混)。
3.在两颗线段树之间建边,入树的底层1指向出树的底层1,入树的底层2指向出树的底层2。可以理解为,进入这个节点后就可以离开这个节点,边权依旧为零。
4.修改,额外开一个新的节点作为中转站(博主试过开两个中间节点的打法,感觉有点奇怪,没试过不开中间节点的,感兴趣的读者可以试一试),把我们要修改的区间分成约log个小区间,和求区间和有点像,就是把[1,4]拆成[1,3]和4这种(自己去画画线段树吧),然后让出树的若干区间指向这个节点,同时带上边权,这道题就是1喽。然后让这个节点再次指向入树的若干区间,权值为零(这块可能也得好好理解一下),别忘了这可是双向边,倒过来在连一遍,就是重复操作4一次,记得新开点,权值给对(这么看来这个中转站好像就只是让代码好打了一些,没有什么实际意义,因为你直接连这两个节点也可以,但是大家可以自己脑补一下代码,可能略恐怖,当然,你是神犇你任性)。
剩下的就是dijkstra了,没学的自己去学,想看板子的给个小链接:最短路
自行理解吧,博主也得去在画画,思考思考。
代码里有个pos数组,是指原题某个数对应的线段树节点,也就是双向存储的另一方。
复杂度的话,网上有各种各样的版本,博主给个大概,边数最坏大约是O(6n+2mlogn),点数大约是O(6n+m),那么根据dijkstra的复杂度为O((n+m)logn)(一般n的规模不大于m时,近似成O(mlogn)) 这样把n代入边数,m代入点数,比原来的O(n*m^2)强了好多。
#include<iostream> #include<algorithm> #include<cmath> #include<cstring> #include<cstdio> #include<vector> #include<queue> #include<stack> #include<set> #include<map> using namespace std; struct EDGE{ int ed,nex,w; }edge[20000001];int first[20000001],num; struct node{ int id,num; friend bool operator < (const node &a,const node &b){ return a.num>b.num; } }now; int dis[20000001];bool v[20000001]; struct Sengment_Tree{ int ls,rs,l,r; }tr[20000001],tc[20000001];//入树,出树。 int pos[20000001],p1,p2,tot,root1,root2; int n,m,P; int read(){ int sum=0,f=1;char x=getchar(); while(x<'0'||x>'9'){ if(x=='-') f=-1; x=getchar(); }while(x>='0'&&x<='9'){ sum=sum*10+x-'0'; x=getchar(); }return sum*f; } void add(int st,int ed,int w){ // cout<<" st="<<st<<" ed="<<ed<<" w="<<w<<endl; edge[++num].ed=ed; edge[num].w=w; edge[num].nex=first[st]; first[st]=num; } void buildtc(int &p,int l,int r){ p=++tot; tc[p].l=l;tc[p].r=r; if(l==r) return ; int mid=l+r>>1; buildtc(tc[p].ls,l,mid); buildtc(tc[p].rs,mid+1,r); add(tc[p].ls,p,0); add(tc[p].rs,p,0); } void buildtr(int &p,int l,int r){ p=++tot; tr[p].l=l;tr[p].r=r; if(l==r){ pos[l]=p; return ; } int mid=l+r>>1; buildtr(tr[p].ls,l,mid); buildtr(tr[p].rs,mid+1,r); add(p,tr[p].ls,0); add(p,tr[p].rs,0); } void buildmid(int p){ if(tc[p].l==tc[p].r){ add(pos[tc[p].l],p,0); return ; } buildmid(tc[p].ls); buildmid(tc[p].rs); } void changetc(int p,int l,int r){ if(l<=tc[p].l&&tc[p].r<=r){ add(p,p1,1); return; } int mid=tc[p].l+tc[p].r>>1; if(l<=mid) changetc(tc[p].ls,l,r); if(r>mid) changetc(tc[p].rs,l,r); } void changetr(int p,int l,int r){ if(l<=tr[p].l&&tr[p].r<=r){ add(p1,p,0); return ; } int mid=tr[p].l+tr[p].r>>1; if(l<=mid) changetr(tr[p].ls,l,r); if(r>mid) changetr(tr[p].rs,l,r); } void dijkstra(int st){ memset(dis,0x7f,sizeof(dis)); priority_queue<node>q; dis[pos[st]]=0; now.id=pos[st];now.num=0; q.push(now); while(!q.empty()){ now=q.top();q.pop(); int x=now.id; if(v[x]) continue; v[x]=1; for(int i=first[x];i;i=edge[i].nex){ int y=edge[i].ed; if(dis[y]>dis[x]+edge[i].w){ dis[y]=dis[x]+edge[i].w; now.id=y;now.num=dis[y]; q.push(now); } } } } void change(int a,int b,int c,int d){ p1=++tot; changetc(root1,a,b); changetr(root2,c,d); } int main(){ /*freopen("11.in","r",stdin); freopen("11.out","w",stdout);*/ n=read();m=read();P=read(); buildtc(root1,1,n);buildtr(root2,1,n);buildmid(root1); for(int i=1,a,b,c,d;i<=m;i++){ a=read();b=read();c=read();d=read(); change(a,b,c,d); change(c,d,a,b); } dijkstra(P); for(int i=1;i<=n;i++) printf("%d ",dis[pos[i]]); return 0; }
出树子指父,
入树父指儿。
横叉入指出,
新边指两边。