【BZOJ】【P2104】【Wc2009】【shortest】【题解】【线段树】

本文探讨了一种使用线段树优化解决最短路径问题的方法,通过维护列信息并进行区间合并,实现了高效的计算过程。该方法特别适用于路径可能绕圈的情况,并在查询时需要考虑多个区间的情况。通过具体的代码实现,展示了如何构建线段树并更新节点值以求解最短路径。
摘要由CSDN通过智能技术生成

传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=2104

一开始想了分块做法,然后发现答案可能绕一圈再回来就放弃了

线段树维护列

每个节点记录ll[6][6],rr[6][6],lr[6][6]分别表示区间左边界/右边界/左右边界点对之间只通过节点控制的区间的最短路

考虑合并两个区间

仿照bil125神,仿照bill125神,用ls表示左子树,rs表示右子树

UPD:少打了一个'l'被喷了T_T

lm[6][6]和rm[6][6]表示从左边界/右边界出发,到自己所在子树的另一边界必须经过另一子树且终点在子树中只有一个点的最短路

lm[i][j]=min(lm[i][j],ls.lr[i][k]+rs.ll[k][j]+ls.rr[j][j])

lm直观上是这样的,rm同理


计算完了lm,rm来更新父节点

a.ll[i][j]=min(a.ll[i][j],lm[i][k]+ls.lr[j][k]-ls.rr[k][k])

直观如下,rr同理


a.lr[i][j]=min(a.lr[i][j],ls.lr[i][k]+rs.lr[k][j]);
a.lr[i][j]=min(a.lr[i][j],lm[i][k]+rm[k][j]-ls.rr[k][k]-rs.ll[k][k]);

lr有两种可能,一种是直接从左往右穿过去,很简单不画了

另一种如下


正确性是因为行数只有6,最多拐3次弯

修改重构一条链

查询需要注意只用一个区间是不够的,需要1..l + l..r +r..n三个区间用上面的方法做

于是这题就是一个常数奇大无比的线段树

Code:

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
const int inf=1061109567;
int n;
int a[6][maxn];
struct node{
	int ll[6][6],rr[6][6],lr[6][6];
	void clear(){memset(ll,0x3f,sizeof ll);memset(rr,0x3f,sizeof rr);
				memset(lr,0x3f,sizeof lr);}
	bool empty()const{return ll[0][0]==inf;}	
}t[maxn<<2];
node operator+(const node &ls,const node &rs){
	static int lm[6][6],rm[6][6];
	if(ls.empty())return rs;if(rs.empty())return ls;
	node a;a.clear();
	memset(lm,0x3f,sizeof lm);memset(rm,0x3f,sizeof rm);
	for(int k=0;k<6;k++)for(int i=0;i<6;i++)for(int j=0;j<6;j++)
	lm[i][j]=min(lm[i][j],ls.lr[i][k]+rs.ll[k][j]+ls.rr[j][j]),
	rm[i][j]=min(rm[i][j],rs.ll[i][i]+ls.rr[i][k]+rs.lr[k][j]);
	for(int i=0;i<6;i++)
	for(int j=0;j<6;j++){
		a.ll[i][j]=ls.ll[i][j];a.rr[i][j]=rs.rr[i][j];a.lr[i][j]=inf;
		for(int k=0;k<6;k++){
			a.ll[i][j]=min(a.ll[i][j],lm[i][k]+ls.lr[j][k]-ls.rr[k][k]);
			a.rr[i][j]=min(a.rr[i][j],rm[k][i]+rs.lr[k][j]-rs.ll[k][k]);
			a.lr[i][j]=min(a.lr[i][j],ls.lr[i][k]+rs.lr[k][j]);
			a.lr[i][j]=min(a.lr[i][j],lm[i][k]+rm[k][j]-ls.rr[k][k]-rs.ll[k][k]);
		}
	}return a;
}
#define lson i<<1,l,(l+r)/2
#define rson i<<1|1,(l+r)/2+1,r
#define ls i<<1
#define rs i<<1|1
void init(int i,int l){
	int sum[6];
	for(int j=0;j<6;j++)sum[j]=(j?sum[j-1]:0)+a[j][l];
	for(int j=0;j<6;j++)for(int k=0;k<6;k++)
	t[i].ll[j][k]=t[i].rr[j][k]=t[i].lr[j][k]=sum[max(j,k)]-(min(j,k)?sum[min(j,k)-1]:0);
	return;
}
void build(int i,int l,int r){
	if(l==r){
		init(i,l);
		return;
	}build(lson);build(rson);
	t[i]=t[ls]+t[rs];
}
void Change(int i,int l,int r,int ps){
	if(l==r){init(i,l);return;}
	if(ps<=(l+r)/2)Change(lson,ps);
	else Change(rson,ps);
	t[i]=t[ls]+t[rs];
}
node Qmin(int i,int l,int r,int l0,int r0){
	if(l0<=l&&r0>=r)return t[i];
	node ans;ans.clear();
	if(l0<=(l+r)/2)ans=Qmin(lson,l0,r0)+ans;
	if(r0>(l+r)/2)ans=ans+Qmin(rson,l0,r0);
	return ans;
}	
#undef ls
#undef rs
int Qmin(int sx,int sy,int tx,int ty){
	node ls=Qmin(1,1,n,1,sy);
	node md=Qmin(1,1,n,sy,ty);
	node rs=Qmin(1,1,n,ty,n);
	int ans=md.lr[sx][tx];
	for(int i=0;i<6;i++)for(int j=0;j<6;j++){
		ans=min(ans,md.ll[sx][i]+ls.rr[i][j]+md.lr[j][tx]-a[i][sy]-a[j][sy]);
		ans=min(ans,md.lr[sx][i]+rs.ll[i][j]+md.rr[j][tx]-a[i][ty]-a[j][ty]);
		ans=min(ans,ls.rr[sx][i]+md.lr[i][j]+rs.ll[j][tx]-a[i][sy]-a[j][ty]);
	}return ans;
}
int getint(){
	int res=0;char c=getchar();
	while(!isdigit(c))c=getchar();
	while(isdigit(c))res=res*10+c-'0',c=getchar();
	return res;
}
int main(){
	n=getint();
	for(int i=0;i<6;i++)
	for(int j=1;j<=n;j++)
	a[i][j]=getint();
	build(1,1,n);
	int m=getint();
	while(m--){
		int op=getint();
		if(op==1){
			int x=getint(),y=getint(),z=getint();
			a[x-1][y]=z;
			Change(1,1,n,y);
		}else{
			int x=getint(),y=getint(),z=getint(),w=getint();
			if(y>w){swap(x,z);swap(y,w);}
			printf("%d\n",Qmin(x-1,y,z-1,w));
		}
	}
	return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值