#Dijkstra,前后缀优化建图#ABC232G - Modulo Shortest Path

题目传送门


分析

考虑按 \(b\) 从小到大排序,那么对于每一种 \(a\) 有一段前缀是不需要减 \(m\) 的;而剩下一段后缀是要减 \(m\) 的。

那么对于这段前缀让 \(i\) 连向 \(pre_{rk_j}\),边权为 \(a_i\)\(pre_{rk_j}\)\(pre_{rk_{j-1}}\) 边权为 \(0\)

\(pre_{rk_j}\)\(rk_j\) 边权为 \(b_{rk_j}\),问题是如果减 \(m\) 的话产生负权边无法使用 Dijkstra,因此

\(i\) 连向 \(suf_{rk_j}\),边权为 \(a_i+b_{rk_j}-m\)\(suf_{rk_j}\)\(suf_{rk_{j+1}}\) 边权为 \(b_{rk_{j+1}}-b_{rk_j}\)

\(suf_{rk_j}\)\(rk_j\) 边权为 \(0\),通过对 \(b\) 的差分实现转化为非负权边,这样最多 \(6n\) 条边,比线段树找新加入集合的点要好写得多。


代码

#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
const int N=600011; struct node{int y,w,next;}e[N<<1];
typedef long long lll; lll dis[N]; priority_queue<pair<lll,int> >q;
int a[N],b[N],n,m,pre[N],suf[N],as[N],et,rk[N];
bool cmp(int x,int y){return b[x]<b[y];}
void add(int x,int y,int w){
	e[++et]=(node){y,w,as[x]},as[x]=et;
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n>>m;
	for (int i=1;i<=n;++i) cin>>a[i],pre[i]=i+n,rk[i]=i;
	for (int i=1;i<=n;++i) cin>>b[i],suf[i]=i+n*2;
	sort(rk+1,rk+1+n,cmp);
	for (int i=1;i<n;++i) add(suf[rk[i]],suf[rk[i+1]],b[rk[i+1]]-b[rk[i]]);
	for (int i=n-1;i;--i) add(pre[rk[i+1]],pre[rk[i]],0);
	for (int i=1;i<=n;++i) add(suf[rk[i]],rk[i],0),add(pre[rk[i]],rk[i],b[rk[i]]);
	for (int i=1;i<=n;++i){
		if (a[i]+b[rk[n]]<m){
			add(i,pre[rk[n]],a[i]);
		}else if (a[i]+b[rk[1]]>=m){
			add(i,suf[rk[1]],a[i]+b[rk[1]]-m);
		}else{
			int l=1,r=n;
			while (l<r){
				int mid=(l+r)>>1;
				if (a[i]+b[rk[mid]]>=m) r=mid;
				    else l=mid+1;
			}
			add(i,pre[rk[l-1]],a[i]);
			add(i,suf[rk[l]],a[i]+b[rk[l]]-m);
		}
	}
	q.push(make_pair(0,1));
	for (int i=2;i<=3*n;++i) dis[i]=1e18;
	while (!q.empty()){
		pair<lll,int>t=q.top(); q.pop();
		if (t.first!=-dis[t.second]) continue;
		for (int i=as[t.second];i;i=e[i].next)
		if (dis[e[i].y]>dis[t.second]+e[i].w){
			dis[e[i].y]=dis[t.second]+e[i].w;
			q.push(make_pair(-dis[e[i].y],e[i].y));
		}
	}
	cout<<dis[n];
	return 0;
}
posted @ 2025-12-02 19:58  lemondinosaur  阅读(5)  评论(0)    收藏  举报