Description
Seter建造了一个很大的星球,他准备建造N个国家和无数双向道路。N个国家很快建造好了,用1..N编号,但是他发现道路实在太多了,他要一条条建简直是不可能的!于是他以如下方式建造道路:(a,b),(c,d)表示,对于任意两个国家x,y,如果a<=x<=b,c<=y<=d,那么在xy之间建造一条道路。Seter保证一条道路不会修建两次,也保证不会有一个国家与自己之间有道路。
Seter好不容易建好了所有道路,他现在在位于P号的首都。Seter想知道P号国家到任意一个国家最少需要经过几条道路。当然,Seter保证P号国家能到任意一个国家。
注意:可能有重边
N<=500000,M<=100000。
1<=A<=B<=N,1<=C<=D<=N。
Solution
显然直接连边时间和空间复杂度都不能接受,考虑更加优秀的方法
回想类比一下倍增并查集,这里建两棵线段树,一棵只从儿子连向父亲记为up树(名字随意啦,一棵只从父亲连向儿子记为down树。修改的时候查询线段树上的区间,并同时新建节点,分别从up树连向新节点、新节点连向down树,边权为1
为什么需要新建节点呢?我们知道一个区间最多对应logn个线段树上的节点,那么直接连边就是log^2n条,通过中继点连边则是logn级别的,这样会少一些边
桦徒举栗
注意到这里是一个稠密图,考虑用堆优dij更优秀
Code
#include <stdio.h>
#include <string.h>
#include <queue>
#define rep(i,st,ed) for (int i=st;i<=ed;++i)
#define fill(x,t) memset(x,t,sizeof(x))
#define fi first
#define se second
const int INF=0x3f3f3f3f;
const int N=500005;
const int E=3000005;
struct edge {int y,w,next;} e[E*10];
struct data {
int x,v;
bool operator <(const data &b) const {
return v>b.v;
}
} ;
struct treeNode {int l,r;} t[N*10];
std:: priority_queue <data> heap;
bool vis[E];
int dis[E],pos[N],tot,rec;
int ls[E],edCnt;
int read() {
int x=0,v=1; char ch=getchar();
for (;ch<'0'||ch>'9';v=(ch=='-')?(-1):(v),ch=getchar());
for (;ch<='9'&&ch>='0';x=x*10+ch-'0',ch=getchar());
return x*v;
}
void add_edge(int x,int y,int w) {
e[++edCnt]=(edge) {y,w,ls[x]}; ls[x]=edCnt;
// printf("%d %d %d\n", x,y,w);
}
void dijkstra(int st) {
fill(dis,63); dis[st]=0;
heap.push((data){st,dis[st]});
rep(i,1,tot) {
if (heap.empty()) return ;
data now=heap.top(); heap.pop();
while (!heap.empty()&&vis[now.x]) now=heap.top(),heap.pop();
if (vis[now.x]) return ;
vis[now.x]=1;
for (int i=ls[now.x];i;i=e[i].next) {
if (!vis[e[i].y]&&now.v+e[i].w<dis[e[i].y]) {
dis[e[i].y]=now.v+e[i].w;
heap.push((data){e[i].y,dis[e[i].y]});
}
}
}
}
void dij(int p)
{
for (int i=1;i<=tot;i++) dis[i]=INF;
dis[pos[p]]=0;
data u;u.x=pos[p];u.v=0;
heap.push(u);
for (int j=1;j<=tot;j++)
{
if (heap.empty()) break;
data u=heap.top();
heap.pop();
while (!heap.empty()&&vis[u.x]) u=heap.top(),heap.pop();
if (vis[u.x]) break;
int x=u.x;vis[x]=1;
for (int i=ls[x];i;i=e[i].next)
if (!vis[e[i].y]&&dis[x]+e[i].w<dis[e[i].y])
{
dis[e[i].y]=dis[x]+e[i].w;
u.x=e[i].y;u.v=dis[e[i].y];
heap.push(u);
}
}
}
void modify(int now,int tl,int tr,int l,int r,int neww,int opt) {
if (l>r) return ;
if (tl==l&&tr==r) {
if (opt) add_edge(now,neww,1);
else add_edge(neww,now,1);
return ;
}
int mid=(tl+tr)>>1;
if (r<=mid) modify(t[now].l,tl,mid,l,r,neww,opt);
else if (l>mid) modify(t[now].r,mid+1,tr,l,r,neww,opt);
else {
modify(t[now].l,tl,mid,l,mid,neww,opt);
modify(t[now].r,mid+1,tr,mid+1,r,neww,opt);
}
}
void build_tree(int &now,int tl,int tr,int opt) {
t[now=++tot]=(treeNode) {0,0};
if (!opt) add_edge(now,now-rec,0);
if (tl==tr) {
if (opt) pos[tl]=now;
return ;
}
int mid=(tl+tr)>>1;
build_tree(t[now].l,tl,mid,opt);
build_tree(t[now].r,mid+1,tr,opt);
if (opt) {
add_edge(t[now].l,now,0);
add_edge(t[now].r,now,0);
} else {
add_edge(now,t[now].l,0);
add_edge(now,t[now].r,0);
}
}
int main(void) {
freopen("data.in","r",stdin);
freopen("myp.out","w",stdout);
int n=read(),m=read(),st=read();
int root1,root2;
build_tree(root1,1,n,1); rec=tot;
build_tree(root2,1,n,0);
rep(i,1,m) {
int a=read(),b=read(),c=read(),d=read();
modify(root1,1,n,a,b,++tot,1);
modify(root2,1,n,c,d,tot,0);
modify(root1,1,n,c,d,++tot,1);
modify(root2,1,n,a,b,tot,0);
}
// dijkstra(pos[st]);
dij(st);
rep(i,1,n) printf("%d\n", dis[pos[i]]/2);
return 0;
}

本文介绍了一种处理大规模稠密图的最短路径算法,利用线段树和堆优化Dijkstra算法来解决从一个特定节点到达其他所有节点所需的最小边数问题。通过巧妙地构造两棵树来减少边的数量,提高了算法效率。

1667

被折叠的 条评论
为什么被折叠?



