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

浙公网安备 33010602011771号