[浅谈] 同余最短路
\(\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\) ,余数加一(或归零),答案加一,建边 \((u,(u+1)\%K,1)\) 。
- 如果乘 \(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;
}

浙公网安备 33010602011771号