[浅谈] 同余最短路

\(\color{red}\text{总述}\)

所谓同余最短路,就是把余数相同的情况归为一类,然后找到形成这种情况的最短路径。

\(\color{purple}\text{P3403 跳楼机}\)

我们假设只能跳 \(x\) 步。那么可以达到的楼层是 \(x,2x,3x,4x\) ,他们的共同点是 \(\%x=0\)

那么现在再加个可以跳 \(y\) 步(若 \(y\) 不是 \(x\) 的倍数且 \(y<x\)),那么我们可以跳到 \(x+y,2x+y,3x+y\)\(\%x=y\) 的点。或许还有更多种类,因此我们可以把对 \(x\) 取余相同的数归为一类,找到这类数的最小数,那么这类数大于等于最小数的数都可以表示了。

我们可以把每类数看做一个点,假设目前点为 \(u\) ,那么我们跳 \(y\) 层时,相当于到了 \((u+y)%x\) 点,然后最小层数用权值求。我们便可以建一条边 \((u,(y+u)\%x,y)\)

以此类推,建完所有边后,跑一边 \(djk\),那么每类数的最小值知道,有多少个就知道,答案就知道了。

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e5+110;
int read(){
	int x=0,f=1;char c=getchar();
	while(c>'9' || c<'0'){if(c=='-')f=-1;c=getchar();}
	while(c>='0' && c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
	return x*f;
}
int h,x,y,z;
int dis[N],head[N],to[N*5],last[N*5],w[N*5],tot;
void add(int u,int v,int t){to[++tot]=v,w[tot]=t,last[tot]=head[u],head[u]=tot;return;}
struct node{
	int dis,u;
	bool operator<(const node A)const{return A.dis<dis;}
};
void dijkstra(){
	memset(dis,0x3f,sizeof(dis));dis[0]=0;
	priority_queue<node>q;q.push((node){0,0});
	while(!q.empty()){
		int u=q.top().u;q.pop();
		for(int i=head[u];i;i=last[i]){
			int v=to[i];
			if(dis[v]>dis[u]+w[i]){
				dis[v]=dis[u]+w[i];
				q.push((node){dis[v],v});
			}
		}
	}
	return;
}
signed main(){
	h=read()-1,x=read(),y=read(),z=read();
	for(int i=0;i<x;i++){
		add(i,(i+y)%x,y);
		add(i,(i+z)%x,z); 
	}
	dijkstra();int ans=0;
	for(int i=0;i<x;i++)if(h>=dis[i])ans+=(h-dis[i])/x+1;
	printf("%lld\n",ans);
	return 0;
} 

\(\color{purple}\text{P2371 [国家集训队]墨墨的等式}\)

\(x\) 看作跳的层数, \(b\) 看作最终跳到哪里,那么就与上题一样了。

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e5+110;
int read(){
	int x=0,f=1;char c=getchar();
	while(c>'9' || c<'0'){if(c=='-')f=-1;c=getchar();}
	while(c>='0' && c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
	return x*f;
}
int n,l,r,a[N];
int dis[N],head[N],to[N*13],last[N*13],w[N*13],tot;
void add(int u,int v,int t){to[++tot]=v,w[tot]=t,last[tot]=head[u],head[u]=tot;return;}
struct node{
	int dis,u;
	bool operator<(const node A)const{return A.dis<dis;}
};
void dijkstra(){
	memset(dis,0x3f,sizeof(dis));dis[0]=0;
	priority_queue<node>q;q.push((node){0,0});
	while(!q.empty()){
		int u=q.top().u;q.pop();
		for(int i=head[u];i;i=last[i]){
			int v=to[i];
			if(dis[v]>dis[u]+w[i]){
				dis[v]=dis[u]+w[i];
				q.push((node){dis[v],v});
			}
		}
	}
	return;
}
signed main(){
	n=read(),l=read(),r=read();
	for(int i=1;i<=n;i++)a[i]=read();
	for(int i=0;i<a[1];i++){
		for(int j=2;j<=n;j++)
			add(i,(i+a[j])%a[1],a[j]);
	}
	dijkstra();int ans=0;
	for(int i=0;i<a[1];i++){
		if(r>=dis[i])ans+=(r-dis[i])/a[1]+1;
		if(l-1>=dis[i])ans-=(l-1-dis[i])/a[1]+1;
	}
	printf("%lld\n",ans);
	return 0;
} 

\(\color{purple}\text{P2662 牛场围栏}\)

还是按 \(x\) 的余数分类,求出没类数最小表示长度 \(len[i]\), 答案就是 \(\max(len[i]-x)\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=3010;
int read(){
	int x=0,f=1;char c=getchar();
	while(c>'9' || c<'0'){if(c=='-')f=-1;c=getchar();}
	while(c>='0' && c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
	return x*f;
}
int n,m,k,bk[N],a[N];
int gcd(int a,int b){
	return b==0?a:gcd(b,a%b);
}
int dis[N],head[N],to[N*3000],last[N*3000],w[N*3000],tot;
void add(int u,int v,int t){to[++tot]=v,w[tot]=t,last[tot]=head[u],head[u]=tot;return;}
struct node{
	int dis,u;
	bool operator<(const node A)const{return A.dis<dis;}
};
void dijkstra(){
	memset(dis,0x3f,sizeof(dis));dis[0]=0;
	priority_queue<node>q;q.push((node){0,0});
	while(!q.empty()){
		int u=q.top().u;q.pop();
		for(int i=head[u];i;i=last[i]){
			int v=to[i];
			if(dis[v]>dis[u]+w[i]){
				dis[v]=dis[u]+w[i];
				q.push((node){dis[v],v});
			}
		}
	}
	return;
}
int main(){
	n=read(),m=read();
	for(int i=1;i<=n;i++){
		int t=read();
		for(int i=t;i>=max(1,t-m);i--)bk[i]=1;
	}
	for(int i=1;i<=3000;i++)
		if(bk[i])a[++k]=i;
	int t=a[1];
	for(int i=2;i<=k;i++)t=gcd(t,a[i]);
	if(t!=1 || a[1]==1){
		printf("-1\n");
		return 0;
	}
	for(int u=0;u<a[1];u++)
		for(int i=2;i<=k;i++)
			add(u,(u+a[i])%a[1],a[i]);
	dijkstra();int ans=0;
	for(int i=0;i<a[1];i++){
		ans=max(ans,dis[i]-a[1]);
	}
	printf("%d\n",ans);
	return 0;
}

\(\color{purple}\text{[ABC077D] Small Multiple}\)

把数按 \(\%K\) 的余数分类,权值为数位累加和,答案就是 \(\%k=0\) 的最小表示法。

  1. 如果把个位 \(+1\) ,余数加一(或归零),答案加一,建边 \((u,(u+1)\%K,1)\)
  2. 如果乘 \(10\), 答案不变,建边 \((u,(10u)\%K,0)\)
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+110;
int read(){
	int x=0,f=1;char c=getchar();
	while(c>'9' || c<'0'){if(c=='-')f=-1;c=getchar();}
	while(c>='0' && c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
	return x*f;
}
int k;
int dis[N],head[N],to[N*3],last[N*3],w[N*3],tot;
void add(int u,int v,int t){to[++tot]=v,w[tot]=t,last[tot]=head[u],head[u]=tot;return;}
struct node{
	int dis,u;
	bool operator<(const node A)const{return A.dis<dis;}
};
void dijkstra(){
	dis[0]=2e9;
	for(int i=1;i<k;i++){
		int j=i;
		while(j)dis[i]+=j%10,j/=10;		
	}
	priority_queue<node>q;
	for(int i=1;i<k;i++)q.push((node){dis[i],i});
	while(!q.empty()){
		int u=q.top().u;q.pop();
		for(int i=head[u];i;i=last[i]){
			int v=to[i];
			if(dis[v]>dis[u]+w[i]){
				dis[v]=dis[u]+w[i];
				q.push((node){dis[v],v});
			}
		}
	}
	return;
}
int main(){
	k=read();
	for(int i=0;i<k;i++){
		add(i,(i+1)%k,1);
		add(i,(i*10)%k,0);
	}
	dijkstra();
	printf("%d\n",dis[0]);
	return 0;
}
posted @ 2023-05-05 15:42  FJOI  阅读(75)  评论(0)    收藏  举报