线段树优化建图

可持久化

线段树优化建图

两道相对模板的例题,都是线段树优化建图之后跑最短路。

CF786B

P6348

分几种情况:

  • 点向点连边
  • 点向区间连边
  • 区间向点连边
  • 区间向区间连边

建树

显然,如果直接建图,每次能建立 \(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);
}

建图

  1. 点连点

作为最简单的一种情况,直接在出树和入树的两个叶子节点之间建边即可。

if(op==1)
{
	int u=re(),v=re(),w=re();
	ins(leafO[u],leafI[v],w+0ll);
}
  1. 点连区间

在入树上找到对应的区间,然后建边。

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);
}
  1. 区间连点

和 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);
}
  1. 区间连区间

对于这种情况,并不是对应区间向对应区间全部连边,而是找一个中介点,出树对应区间向中介点连边,中介点向入树对应区间连边。

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;
}

当然,线段树优化建图不止用于最短路,多数图论问题都可以利用线段树优化建图。

树套树

posted @ 2022-09-08 20:40  Zvelig1205  阅读(267)  评论(0编辑  收藏  举报