zoj 3724 离线+线段树

本文介绍了一种解决离线最短路径问题的有效算法。针对一个由n个节点构成的图,通过预先处理特殊边来高效地回答大量询问。算法分为两个阶段处理不同方向的路径查询,利用线段树维护最短路径。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3724

这是道离线的好题!

首先,关于题意:现在有一个从1~n的n-1条有向边,其中i连向i+1,并且给出了这n-1条边的长度。除了这n-1条边之外,还有m条特殊的有向边。现在规定,特殊的边最多只能走一次。然后给出q组询问,问从u到v的最短路。

其中n<=1e5,m<=2e5,q<=2e5。边权值都是正的


直接在线求我真不是很清楚怎么做,也许根本做不到。。。我是离线做的:

观察询问,<u,v>。这其中v可能是大于u,也可能是小于u的,即一个是往后走,一个是往回走。(对于u==v直接就是0了。。。。)

情况一:对于v>u的情况,我们只要找到u->v之间是否存在一个特殊边x->y,u<=x<y<=v,使总路径更短

情况二:对于v<u的情况,我们必须在u点或u后面找到一条往回走的一条特殊边x->y,且x>=u,y<=v。


定义sum[i]表示i点到n点的长度和

以上两种情况的答案都可表示为:ans[u,v]=sum[u]-sum[v]+min(sum[y]-sum[x]+Wxy)

我们可以将这两种情况分开处理:

情况一:将询问按u,从大到小排序,然后从大到小枚举u点,将起点在u点上的向后的特殊边<x,y>(x==u&&y>x)插入到线段树中的y点上,权值为sum[y]-sum[x]+Wxy。然后处理在u点上的询问<u,v>,即ans[u,v]=sum[u]-sum[v]+min(u点到v点的最小值,0)。注意往后走的时候可以不走特殊边的!

情况二:将询问按v,从小到大排序,然后从小到大枚举v点,将终点在v点上的向前的特殊边<x,y>(y==v&&x<y)插入到线段树中的x点上,权值为sum[y]-sum[x]+Wxy。然后处理在v点上的询问<u,v>,即ans[u,v]=sum[u]-sum[v]+u到n点的最小值。


#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 100010;
typedef long long ll;
const ll INF = 1e16;
int n,m;
ll sum[maxn];
struct Query{
	int id,u,v;
	void set(int u,int v,int id){
		this->id=id;
		this->u=u;
		this->v=v;
	}
}q1[maxn*2],q2[2*maxn];
bool cmp1(const Query &a,const Query &b){
	return a.u>b.u;
}
bool cmp2(const Query &a,const Query &b){
	return a.v<b.v;
}
int top1,top2,top11,top22,node1[maxn],node2[maxn];
ll ans[maxn*2];
struct Side{
	int to,next;
	ll w;
}side1[200010],side2[200010];
void add_side1(int u,int v,ll w){
	side1[top11]=(Side){v,node1[u],w};
	node1[u]=top11++;
}
void add_side2(int u,int v,ll w){
	side2[top22]=(Side){v,node2[u],w};
	node2[u]=top22++;
}

ll val[maxn*4],lazy[maxn*4];
void build(int th,int l,int r){
	val[th]=INF;
	lazy[th]=INF;
	if(l==r)return;
	int mid=(l+r)/2;
	build(th*2,l,mid);
	build(th*2+1,mid+1,r);
}
void insert(int th,int x,int L,int R,ll v){
	if(x==L&&x==R){
		val[th]=min(val[th],v);
		lazy[th]=min(lazy[th],v);
		return;
	}
	int mid=(L+R)/2;
	if(lazy[th]!=INF){
		lazy[th*2]=min(lazy[th],lazy[th*2]);
		val[th*2]=min(val[th*2],lazy[th]);
		lazy[th*2+1]=min(lazy[th],lazy[th*2+1]);
		val[th*2+1]=min(val[th*2+1],lazy[th]);
		lazy[th]=INF;
	}
	if(x<=mid){
		insert(th*2,x,L,mid,v);
	}else{
		insert(th*2+1,x,mid+1,R,v);
	}
	val[th]=min(val[th*2],val[th*2+1]);
}
ll query(int th,int l,int r,int L,int R){
	if(l==L&&r==R){
		return val[th];
	}
	if(lazy[th]!=INF){
		lazy[th*2]=min(lazy[th],lazy[th*2]);
		val[th*2]=min(val[th*2],lazy[th]);
		lazy[th*2+1]=min(lazy[th],lazy[th*2+1]);
		val[th*2+1]=min(val[th*2+1],lazy[th]);
		lazy[th]=INF;
	}
	int mid=(L+R)/2;
	if(r<=mid){
		return query(th*2,l,r,L,mid);
	}else if(l>mid){
		return query(th*2+1,l,r,mid+1,R);
	}else{
		return min(query(th*2,l,mid,L,mid),query(th*2+1,mid+1,r,mid+1,R));
	}
}
int main(){
	while(~scanf("%d%d",&n,&m)){
		for(int i=0;i<n-1;i++){
			scanf("%lld",&sum[i]);
		}
		top11=top22=top1=top2=0;
		sum[n]=0;
		for(int i=n-1;i>=0;i--){
			sum[i]+=sum[i+1];
		}
		memset(node1,-1,sizeof(node1));
		memset(node2,-1,sizeof(node2));
		for(int i=0;i<m;i++){
			int u,v,w;
			scanf("%d%d%d",&u,&v,&w);
			u--;v--;
			if(v>u)add_side1(u,v,(ll)w);
			else if(v<u)add_side2(v,u,(ll)w);
		}
		int q;
		scanf("%d",&q);
		for(int i=0;i<q;i++){
			int u,v;
			scanf("%d%d",&u,&v);
			u--;v--;
			if(v>u){
				q1[top1].set(u,v,i);
				top1++;
			}else{
				q2[top2].set(u,v,i);
				top2++;
			}
		}
		sort(q1,q1+top1,cmp1);
		sort(q2,q2+top2,cmp2);
		build(1,0,n-1);
		int u=n-1;
		int t1=0,t2=0;
		while(u>=0){
			for(int i=node1[u];i!=-1;i=side1[i].next){
				int v=side1[i].to;
				insert(1,v,0,n-1,side1[i].w+sum[v]-sum[u]);
			}
			while(t1<top1&&q1[t1].u==u){
				ans[q1[t1].id]=sum[u]-sum[q1[t1].v]+min(query(1,u,q1[t1].v,0,n-1),(ll)0);
				t1++;
			}
			u--;
		}
		build(1,0,n-1);
		int v=0;
		while(v<=n-1){
			for(int i=node2[v];i!=-1;i=side2[i].next){
				int u=side2[i].to;
				insert(1,u,0,n-1,side2[i].w+sum[v]-sum[u]);
			}
			while(t2<top2&&q2[t2].v==v){
				if(q2[t2].u!=v)ans[q2[t2].id]=sum[q2[t2].u]-sum[v]+query(1,q2[t2].u,n-1,0,n-1);
				else ans[q2[t2].id]=0;
				t2++;
			}
			v++;
		}
		for(int i=0;i<q;i++){
			printf("%lld\n",ans[i]);
		}
	}
}
//void insert(int th,int x,int L,int R,int v){


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值