【算法学习】同余最短路
前言
给定 \(n\) 个整数,求这 \(n\) 个整数能拼凑出多少的其他整数,类似这样的题型就可以用同余最短路来实现。
【同余最短路模板】P3403 跳楼机
本题可以抽象为如下问题:
给定四个正整数 \(x,y,z,H\),求有多少个整数 \(d \in [1,H]\) 满足 \(ax+by+cz=d\),其中 \(a,b,c\) 都是非负整数。
我们定义 \(k=by+cz\),而这个 \(k\) 有特殊的性质就是 \(k\equiv{k+x}\equiv{k+2x}\equiv{\cdots }\equiv{k+ax} \pmod x\),而且一定有 \(k \in [0,x-1]\),也就是说我们知道了一个 \(k\) 且 \(k\) 合法的话我们就可以一直加 \(x\) 直到超过 \(H\) 限制。
当然如果 \(k\) 互不相同那 \(k+x\) 也互不相同。
所以我们有 \(dis_i\) 为使得 \(k\bmod x=i\) 的最低层数(层数越低可加的 \(x\) 就越多),然后我们将 \([0,x-1]\) 每个点都视为一个单独的节点,我们进行以下建边方式:
-
\(i\) 向 \((i+y)\bmod x\) 建一条权值为 \(y\) 的有向边。
-
\(i\) 向 \((i+z)\bmod x\) 建一条权值为 \(z\) 的有向边。
明显的 \(dis_0\) 一定为 \(0\),所以我们以这个点为起点跑最短路得到每个点的 \(dis_i\)。
因为我们的点的范围为 \([0,x-1]\) 所以要将 \(H-1\) 使得起始楼层为 \(0\)。
楼层的最大限制为 \(h\),那你从 \(k\) 通过加 \(x\) 最多可以加 \(\lfloor \frac{h-dis_i}{x} \rfloor+1\) 个 \(x\)(加一是因为 \(x\) 的系数可以为 \(0\))。
那么最后的答案就是:
代码部分:
#include <bits/stdc++.h> #define int long long const int N=1e6; const int inf=1e16; using namespace std; int head[N]; int cnt=1; struct ss{ int v,next,w; }e[N<<1]; void add(int u,int v,int w){ e[++cnt].v=v; e[cnt].w=w; e[cnt].next=head[u]; head[u]=cnt; } int h,x,y,z; int vis[N]; int dis[N]; queue<int> q; void spfa(int s){ dis[s]=0; vis[s]=1; q.push(s); while(!q.empty()){ int x=q.front(); q.pop(); vis[x]=0; for(int i=head[x];i;i=e[i].next){ int y=e[i].v; if(dis[y]>dis[x]+e[i].w){ dis[y]=dis[x]+e[i].w; if(!vis[y]){ q.push(y); } } } } } signed main(){ ios::sync_with_stdio(false); cin.tie(nullptr); cin>>h>>x>>y>>z; h--; for(int i=0;i<x;i++){ dis[i]=inf; add(i,(i+y)%x,y); add(i,(i+z)%x,z); } spfa(0); int ans=0; for(int i=0;i<x;i++){ if(dis[i]<=h&&dis[i]!=inf){ ans+=(h-dis[i])/x+1; } } cout<<ans; return 0; }
D - Small Multiple
考虑 01bfs 就是最短咯,我们从 \(1\) 开始搜索不断压答案大小,再同时判断模 \(k\) 判断是否是倍数。
#include<bits/stdc++.h> using namespace std; int k; int vis[10000005]; struct ss{ int ans,v; }; deque<ss> q; int main() { cin>>k; q.push_front(ss{1,1}); vis[1]=1; while(!q.empty()){ int x=q.front().ans,y=q.front().v; q.pop_front(); if(x==0){ cout<<y; return 0; } if(!vis[x*10%k]){ q.push_front(ss{x*10%k,y}); vis[x*10%k]=1; } if(!vis[x+1]){ q.push_back(ss{x+1,y+1}); } } return 0; }
P2662 牛场围栏
同样也是同余最短路,我们找到最小的数作为基准,如果所有数有 \(1\) 或者 \(dis\ge inf\) 则输出 \(-1\),否则最大的数是 \(\max dis_i-x\)。
#include <bits/stdc++.h> #define int long long const int N=5100001; const int inf=1e18; using namespace std; int head[2600001]; int cnt=1; struct ss{ int v,next,w; }e[2600001]; void add(int u,int v,int w){ e[++cnt].v=v; e[cnt].w=w; e[cnt].next=head[u]; head[u]=cnt; } int n,l; int a[105]; int vis[2600001]; int dis[2600001]; queue<int> q; void spfa(int s){ dis[s]=0; vis[s]=1; q.push(s); while(!q.empty()){ int x=q.front(); q.pop(); vis[x]=0; for(int i=head[x];i;i=e[i].next){ int y=e[i].v; if(dis[y]>dis[x]+e[i].w){ dis[y]=dis[x]+e[i].w; if(!vis[y]){ q.push(y); } } } } } int tot=0; signed main(){ ios::sync_with_stdio(false); cin.tie(nullptr); cin>>n>>l; for(int i=1;i<=n;i++){ cin>>a[i]; } sort(a+1,a+n+1); int k=max(1ll,a[1]-l); if(k==1){ cout<<-1; return 0; } for(int i=1;i<=n;i++){ for(int j=max(a[i-1]+1,a[i]-l);j<=a[i];j++){ if(j!=k){ for(int z=0;z<k;z++){ add(z,(z+j)%k,j); } } } } for(int i=0;i<k;i++){ dis[i]=inf; } spfa(0); int ans=0; for(int i=0;i<k;i++){ if(dis[i]>inf){ cout<<-1; return 0; } ans=max(ans,dis[i]); } cout<<ans-k; return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)