题干:
Seter建造了一个很大的星球,他准备建造N个国家和无数双向道路。N个国家很快建造好了,用1..N编号,但是他发现道路实在太多了,他要一条条建简直是不可能的!于是他以如下方式建造道路:(a,b),(c,d)表示,对于任意两个国家x,y,如果a<=x<=b,c<=y<=d,那么在x y之间建造一条道路。Seter保证一条道路不会修建两次,也保证不会有一个国家与自己之间有道路。Seter好不容易建好了所有道路,他现在在位于P号的首都。Seter想知道P号国家到任意一个国家最少需要经过几条道路。当然,Seter保证P号国家能到任意一个国家。
注意:可能有重边
题解:
这不裸的dij吗?打完以后发现不妙,这数据范围略神奇……算一算,考一场都可能跑不出来。。。map去重边(成功额外引入log)不怕,交。TLE,54。
这不玩呢吗,把map去了,交。MLE,71……这题考场性价比可能挺高的。尝试改成vector,没省内存,反而更慢了。
发现以前没学过的一个知识点——权值线段树优化建图。
它一般出现某个区间和另一个区间内的若干元素建边时使用,因为一个点一个点的建肯定不可取。
这时,根据我们对线段树的理解,我们可以把线段树上的节点当成图中的节点,根据线段树上一个节点可以表示一个区间这个性质就可以加快建边了。
具体一点,过程如下:
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了。
代码里有个self数组,是指原题某个数对应的线段树节点,也就是双向存储的另一方。
复杂度的话,边数最坏大约是O(6n+2mlogn),点数大约是O(6n+m),那么根据dijkstra的复杂度为O((n+m)logn)
(一般n的规模不大于m时,近似成O(mlogn)) 这样把n代入边数,m代入点数,比原来的O(n*m^2)强了好多。
Code:
1 #include<cstdio> 2 #include<cstring> 3 #include<queue> 4 #define $ 4440000 5 using namespace std; 6 const int L=1<<20|1; 7 char buffer[L],*S,*T; 8 #define getchar() ((S==T&&(T=(S=buffer)+fread(buffer,1,L,stdin),S==T))?EOF:*S++) 9 inline int read(){ 10 char a=getchar(); 11 int sum=0; 12 while(a<'0'||a>'9') a=getchar(); 13 while(a>='0'&&a<='9') sum=(sum<<1)+(sum<<3)+a-'0',a=getchar(); 14 return sum; 15 } 16 int m,n,s,root1,root2,dis[$],self[$],first[$],tot,sz,ls[$],rs[$]; 17 bool vis[$]; 18 struct tree{ int to,next,w; }aa[$*5]; 19 inline int max(int x,int y){ return x>y?x:y; } 20 inline int min(int x,int y){ return x<y?x:y; } 21 inline void addaa(int x,int y,int w=0){ 22 aa[++tot]=(tree){ y,first[x],w }; 23 first[x]=tot; 24 } 25 inline void build(int &root,int l,int r,bool opt){//权值线段树优化建边 26 if(!root) root=++sz; 27 if(l==r){ if(opt) self[l]=sz; return; } 28 //说是两棵树,其实只需建一棵树,另一棵树其实是并不存在的。 29 int mid=(l+r)>>1; 30 build(ls[root],l,mid,opt); build(rs[root],mid+1,r,opt); 31 if(opt) addaa(ls[root],root), addaa(rs[root],root);//出树建正边 32 else addaa(root,ls[root]), addaa(root,rs[root]);//入树建反边 33 } 34 inline void add(int x,int y,int l,int r){//初始时在最下面一层先对应连边 35 if(l==r){ addaa(y,x); return; } 36 int mid=(l+r)>>1; 37 add(ls[x],ls[y],l,mid); add(rs[x],rs[y],mid+1,r); 38 } 39 inline void update(int root,int l,int r,int st,int ed,int c,bool opt){ 40 if(l==st&&r==ed){ 41 if(opt) addaa(root,c); 42 else addaa(c,root); 43 return; 44 } 45 int mid=(l+r)>>1; 46 if(st<=mid) update(ls[root],l,mid,st,min(mid,ed),c,opt); 47 if(ed>mid) update(rs[root],mid+1,r,max(st,mid+1),ed,c,opt); 48 } 49 inline void link(int a,int b,int c,int d){ 50 update(root1,1,n,a,b,++sz,1);//建一个新点,相连,权值为0 51 addaa(sz,sz+1,1);//两新点连边,权值为1 52 update(root2,1,n,c,d,++sz,0); 53 } 54 inline void Dijstra(){ 55 priority_queue< pair<int,int> > q; 56 for(register int i=1;i<=sz;++i) dis[i]=0x7ffffff; 57 dis[self[s]]=0; 58 q.push(make_pair(0,self[s])); 59 while(q.size()){ 60 int x=q.top().second; q.pop(); 61 if(vis[x]) continue; 62 vis[x]=1; 63 for(register int i=first[x];i;i=aa[i].next){ 64 int to=aa[i].to; 65 if(vis[to]) continue; 66 if(dis[to]>dis[x]+aa[i].w){ 67 dis[to]=dis[x]+aa[i].w; 68 q.push(make_pair(-dis[to],to)); 69 } 70 } 71 } 72 } 73 signed main(){ 74 n=read(); m=read(); s=read(); 75 build(root1,1,n,1); build(root2,1,n,0); 76 add(root1,root2,1,n); 77 for(register int i=1,x1,x2,x3,x4;i<=m;++i){ 78 x1=read(),x2=read(),x3=read(),x4=read(); 79 link(x1,x2,x3,x4); link(x3,x4,x1,x2);//注意是双向边 80 } 81 Dijstra(); 82 for(register int i=1;i<=n;++i) printf("%d ",dis[self[i]]); 83 }