【Atcoder】 [ABC232G] Modulo Shortest Path

题目链接

Atcoder方向
Luogu方向

题目解法

考虑暴力 d i j dij dij 时间会爆炸,可以考虑优化边数,使其变成 O ( n ) O(n) O(n) 级别

有一个重要的 t r i c k trick trick 是把 ( a u + b v )    m o d    m (a_u+b_v)\;mod\;m (au+bv)modm 变为 ( − ( − a u ) + b v )    m o d    m (-(-a_u)+b_v)\;mod\;m ((au)+bv)modm,这样的好处是把 u , v u,v u,v 之间的边权变成了在 0 − m 0-m 0m 组成的环上的距离,仔细考虑一下会发现这是对的
如果令 0 0 0 m − 1 m-1 m1 为环, m m m m + n − 1 m+n-1 m+n1 为原始点的话,所以可以考虑建如下的边:

  1. 对于 i ∈ [ 0 , m ) , i\in[0,m), i[0,m) i i i i + 1 i+1 i+1建一条权值为 1 的边
  2. 对于 i ∈ [ m , m + n ) i\in[m,m+n) i[m,m+n) i i i − a i -a_i ai 建一条权值为 0 的边
  3. 对于 i ∈ [ m , m + n ) i\in[m,m+n) i[m,m+n) b i b_i bi i i i 建一条权值为 0 的边

考虑这样的图上 m m m m + n − 1 m+n-1 m+n1 的最短路即为答案

考虑有极大多数点是无用的,考虑只建 − a i , b i -a_i,b_i ai,bi 和原始点这 3 n 3n 3n 个点,然后类似连边跑 d i j dij dij 即可,可以发现,这样的时间复杂度是 O ( n l o g n ) O(nlogn) O(nlogn)

#include <bits/stdc++.h>
#define int long long
using namespace std;
typedef pair<int,int> pii;
const int N(600100);
int n,m,cnt,a[N],b[N],disc[N],dis[N];
bool vis[N];
int e[N<<1],h[N],ne[N<<1],w[N<<1],idx;
priority_queue<pii,vector<pii>,greater<pii> > que;
inline int read(){
	int FF=0,RR=1;
	char ch=getchar();
	for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
	for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
	return FF*RR;
}
void dij(){
	memset(dis,0x3f,sizeof(dis));
	que.push(make_pair(0,cnt+1));
	dis[cnt+1]=0;
	while(!que.empty()){
		int u=que.top().second;que.pop();
		if(vis[u]) continue;
//		cout<<u<<' '<<dis[u]<<'\n';
		vis[u]=1;
		for(int i=h[u];~i;i=ne[i]){
			int v=e[i];
			if(dis[u]+w[i]<dis[v]){
				dis[v]=dis[u]+w[i];
				que.push(make_pair(dis[v],v));
			}
		}
	}
}
void add(int x,int y,int z){ e[idx]=y,w[idx]=z,ne[idx]=h[x],h[x]=idx++;}
signed main(){
	memset(h,-1,sizeof(h));
	n=read(),m=read();
	for(int i=1;i<=n;i++) a[i]=read(),a[i]=(m-a[i])%m;
	for(int i=1;i<=n;i++) b[i]=read();
	for(int i=1;i<=n;i++) disc[++cnt]=a[i],disc[++cnt]=b[i];
	sort(disc+1,disc+cnt+1);
	cnt=unique(disc+1,disc+cnt+1)-disc-1;
//	for(int i=1;i<=cnt;i++) cout<<disc[i]<<' ';cout<<'\n';
	for(int i=1;i<cnt;i++) add(i,i+1,disc[i+1]-disc[i]);
	add(cnt,1,m-disc[cnt]+disc[1]);
	for(int i=1;i<=n;i++){
		a[i]=lower_bound(disc+1,disc+cnt+1,a[i])-disc;
		b[i]=lower_bound(disc+1,disc+cnt+1,b[i])-disc; 
		add(i+cnt,a[i],0),add(b[i],i+cnt,0);
	}
	dij(); 
	printf("%lld",dis[cnt+n]);
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值