线段树优化建图
可持久化
线段树优化建图
两道相对模板的例题,都是线段树优化建图之后跑最短路。
分几种情况:
- 点向点连边
- 点向区间连边
- 区间向点连边
- 区间向区间连边
建树
显然,如果直接建图,每次能建立 \(n^2\) 数量级的边,总边数大概是 \(O(mn^2)\)(因为重边会多次计算),空间复杂度不允许。
对于区间问题,我们有神器:线段树。
既然直接连边太麻烦,那么就可以用线段树上的一个节点代表图上的一个区间的点。
不过,在同一棵线段树上维护这些点会很混乱。所以我们考虑用两棵线段树维护分别这些点的出边和入边,称为 出树、入树。
对于出树,每个区间的两个子区间向当前区间连边权为 0 的边(以下简称 0 边);
对于入树,每个区间向两个子区间连 0 边。
这个是显然的,在小区间和大区间之间穿梭不需要代价。
虽然建两棵线段树,但事实上,入树上的节点 dis 值更改后应该都可以更新出树上的点进一步更新其他节点的 dis 值,所以应该有入树向出树连的 0 边。
而对于叶子节点,还应该有出树向入树连的 0 边(自己到自己的 dis 值为 0)。
处理好细节,代码就是这样:
const int inf=2e6+7;
int n,m,s;
struct edge{
int to;ll val;
edge(int to,ll val):to(to),val(val){}
};
vector<edge>e[inf];
void ins(int x,int y,ll z)
{//vector 存图
e[x].push_back(edge(y,z));
}
struct Seg_Tree{
int le,ri;
int id;
}O[inf],I[inf];//out 出树,in 入树
int leafO[inf],leafI[inf],sum;//存放叶子节点
void build(int i,int l,int r)
{
O[i].le=I[i].le=l,O[i].ri=I[i].ri=r;
O[i].id=++sum,I[i].id=++sum;
ins(I[i].id,O[i].id,0);//入树向出树建 0 边
if(l==r)
{//叶子节点
leafO[l]=O[i].id;
leafI[l]=I[i].id;
ins(O[i].id,I[i].id,0);
return;
}
int lc=i<<1,rc=i<<1|1,mid=(l+r)>>1;
build(lc,l,mid);
build(rc,mid+1,r);
//出树子区间向当前区间连 0 边
ins(O[lc].id,O[i].id,0);
ins(O[rc].id,O[i].id,0);
//入树当前区间向子区间连 0 边
ins(I[i].id,I[lc].id,0);
ins(I[i].id,I[rc].id,0);
}
建图
- 点连点
作为最简单的一种情况,直接在出树和入树的两个叶子节点之间建边即可。
if(op==1)
{
int u=re(),v=re(),w=re();
ins(leafO[u],leafI[v],w+0ll);
}
- 点连区间
在入树上找到对应的区间,然后建边。
void askI(int x,int i,int l,int r,ll k)
{
if(l<=I[i].le&&I[i].ri<=r)
{
ins(leafO[x],I[i].id,k);
return;
}
int mid=(I[i].le+I[i].ri)>>1;
if(l<=mid)askI(x,i<<1,l,r,k);
if(mid<r)askI(x,i<<1|1,l,r,k);
}
- 区间连点
和 2. 差不多,只是换成在出树上找对应区间。
void askO(int i,int l,int r,int x,ll k)
{
if(l<=O[i].le&&O[i].ri<=r)
{
ins(O[i].id,leafI[x],k);
return;
}
int mid=(O[i].le+O[i].ri)>>1;
if(l<=mid)askO(i<<1,l,r,x,k);
if(mid<r)askO(i<<1|1,l,r,x,k);
}
- 区间连区间
对于这种情况,并不是对应区间向对应区间全部连边,而是找一个中介点,出树对应区间向中介点连边,中介点向入树对应区间连边。
void askO(int i,int l,int r,int x)
{
if(l<=O[i].le&&O[i].ri<=r)
{
ins(O[i].id,x,1);
return;
}
int mid=(O[i].le+O[i].ri)>>1;
if(l<=mid)askO(i<<1,l,r,x);
if(mid<r)askO(i<<1|1,l,r,x);
}
void askI(int i,int l,int r,int x)
{
if(l<=I[i].le&&I[i].ri<=r)
{
ins(x,I[i].id,1);
return;
}
int mid=(I[i].le+I[i].ri)>>1;
if(l<=mid)askI(i<<1,l,r,x);
if(mid<r)askI(i<<1|1,l,r,x);
}
最短路
这种题最恶心的地方就是建图,建完图之后跑裸的 dijkstra 就可以了。
当然,最终的答案应该是入树的叶节点的 dis 值。
AC Code:
CF786B
const int inf=2e6+7;
int n,m,s;
struct edge{
int to;ll val;
edge(int to,ll val):to(to),val(val){}
};
vector<edge>e[inf];
void ins(int x,int y,ll z)
{
e[x].push_back(edge(y,z));
}
struct Seg_Tree{
int le,ri;
int id;
}O[inf],I[inf];
int leafO[inf],leafI[inf],sum;
void build(int i,int l,int r)
{
O[i].le=I[i].le=l,O[i].ri=I[i].ri=r;
O[i].id=++sum,I[i].id=++sum;
ins(I[i].id,O[i].id,0);
if(l==r)
{
leafO[l]=O[i].id;
leafI[l]=I[i].id;
ins(O[i].id,I[i].id,0);
return;
}
int lc=i<<1,rc=i<<1|1,mid=(l+r)>>1;
build(lc,l,mid);
build(rc,mid+1,r);
ins(O[lc].id,O[i].id,0);
ins(O[rc].id,O[i].id,0);
ins(I[i].id,I[lc].id,0);
ins(I[i].id,I[rc].id,0);
}
void askO(int i,int l,int r,int x,ll k)
{
if(l<=O[i].le&&O[i].ri<=r)
{
ins(O[i].id,leafI[x],k);
return;
}
int mid=(O[i].le+O[i].ri)>>1;
if(l<=mid)askO(i<<1,l,r,x,k);
if(mid<r)askO(i<<1|1,l,r,x,k);
}
void askI(int x,int i,int l,int r,ll k)
{
if(l<=I[i].le&&I[i].ri<=r)
{
ins(leafO[x],I[i].id,k);
return;
}
int mid=(I[i].le+I[i].ri)>>1;
if(l<=mid)askI(x,i<<1,l,r,k);
if(mid<r)askI(x,i<<1|1,l,r,k);
}
struct node{
int id;ll val;
node(int id,ll val):id(id),val(val){}
bool operator <(const node &b)const
{
return val>b.val;
}
};
priority_queue<node>h;
ll dis[inf];
bool vis[inf];
signed main()
{
n=re();m=re();s=re();
build(1,1,n);
for(int i=1;i<=m;i++)
{
int op=re();
if(op==1)
{
int u=re(),v=re(),w=re();
ins(leafO[u],leafI[v],w+0ll);
}
if(op==2)
{
int u=re(),l=re(),r=re(),k=re();
askI(u,1,l,r,k+0ll);
}
if(op==3)
{
int u=re(),l=re(),r=re(),k=re();
askO(1,l,r,u,k+0ll);
}
}
memset(dis,127,sizeof(dis));
h.push(node(leafO[s],0));
dis[leafO[s]]=0;
while(h.size())
{
int now=h.top().id;h.pop();
if(vis[now])continue;
vis[now]=1;
int len=e[now].size();
for(int i=0;i<len;i++)
{
int p=e[now][i].to;
if(dis[p]>dis[now]+e[now][i].val)
{
dis[p]=dis[now]+e[now][i].val;
h.push(node(p,dis[p]));
}
}
}
for(int i=1;i<=n;i++)
{
if(dis[leafI[i]]==0x7f7f7f7f7f7f7f7f)dis[leafI[i]]=-1;
wr(dis[leafI[i]]),putchar(' ');
}
return 0;
}
P6348
const int inf=1e7+7;
int n,m,s;
struct edge{
int to,val;
edge(int to,int val):to(to),val(val){}
};
vector<edge>e[inf];
void ins(int x,int y,int z)
{
e[x].push_back(edge(y,z));
}
struct Seg_Tree{
int le,ri;
int id;
}O[inf],I[inf];
int leafO[inf],leafI[inf],cnt;
void build(int i,int l,int r)
{
O[i].le=I[i].le=l;O[i].ri=I[i].ri=r;
O[i].id=++cnt;I[i].id=++cnt;
ins(I[i].id,O[i].id,0);
if(l==r)
{
leafO[l]=O[i].id;
leafI[l]=I[i].id;
ins(O[i].id,I[i].id,0);
return;
}
int lc=i<<1,rc=lc|1,mid=(l+r)>>1;
build(i<<1,l,mid);
build(i<<1|1,mid+1,r);
ins(O[lc].id,O[i].id,0);
ins(O[rc].id,O[i].id,0);
ins(I[i].id,I[lc].id,0);
ins(I[i].id,I[rc].id,0);
}
void askO(int i,int l,int r,int x)
{
if(l<=O[i].le&&O[i].ri<=r)
{
ins(O[i].id,x,1);
return;
}
int mid=(O[i].le+O[i].ri)>>1;
if(l<=mid)askO(i<<1,l,r,x);
if(mid<r)askO(i<<1|1,l,r,x);
}
void askI(int i,int l,int r,int x)
{
if(l<=I[i].le&&I[i].ri<=r)
{
ins(x,I[i].id,1);
return;
}
int mid=(I[i].le+I[i].ri)>>1;
if(l<=mid)askI(i<<1,l,r,x);
if(mid<r)askI(i<<1|1,l,r,x);
}
struct node{
int to,val;
node(int to,int val):to(to),val(val){}
bool operator <(const node &b)const
{
return val>b.val;
}
};
priority_queue<node>h;
int dis[inf];
bool vis[inf];
int main()
{
n=re();m=re();s=re();
build(1,1,n);
for(int i=1;i<=m;i++)
{
int a=re(),b=re(),c=re(),d=re();
cnt++;askO(1,a,b,cnt);askI(1,c,d,cnt);
cnt++;askO(1,c,d,cnt);askI(1,a,b,cnt);
}
memset(dis,127,sizeof(dis));
h.push(node(leafO[s],0));
dis[leafO[s]]=0;
while(h.size())
{
int now=h.top().to;h.pop();
if(vis[now])continue;
vis[now]=1;
int len=e[now].size();
for(int i=0;i<len;i++)
{
int p=e[now][i].to;
if(dis[p]>dis[now]+e[now][i].val)
{
dis[p]=dis[now]+e[now][i].val;
h.push(node(p,dis[p]));
}
}
}
for(int i=1;i<=n;i++)
wr(dis[leafI[i]]>>1),putchar('\n');
return 0;
}
当然,线段树优化建图不止用于最短路,多数图论问题都可以利用线段树优化建图。