同余最短路(学习笔记)
简介:
同余最短路,光是看名字就会发现与同余和最短路有关。同余最短路常用来解决的问题是:
当出现形如「给定 \(n\) 个整数,求这 \(n\) 个整数能拼凑出多少的其他整数(\(n\) 个整数可以重复取)」,以及「给定 \(n\) 个整数,求这 \(n\) 个整数不能拼凑出的最小(最大)的整数」,或者「至少要拼几次才能拼出模 \(K\) 余 \(p\) 的数」的问题时可以使用同余最短路的方法。
同余最短路利用同余来构造一些状态,可以达到优化空间复杂度的目的。
原理:
最短路:
我们先分析最短路的原理,我们根据 \(dijkstra\) 求最短路的原理可以发现。我们是根据每一次的对点的遍历,然后不断更新 \(dis\) 数组,即为松弛操作。而一般的更新条件为:
其中的 \(edge\) 表示由 \(x\) 到 \(y\) 的有向边的边权。那么这样更新后的最短路,显然满足一个性质,及:
因为若有不满住该性质的点,那一定会在求最短路时被松弛掉。
那知道这个性质后,来分析同余最短路的性质。
先引入一个问题:P3403 跳楼机
题目引入:
给定四个整数 \(x,y,z,h\),求有多少整数 \(d \in [0,h]\) 使得 \(ax+by+cz=d\) 其中的 \(a,b,c\) 都为非负整数。
称一个数合法当且仅当其可以表示成 \(ax+by+cz\) 的形式,但不一定小于等于 \(H\)
那假设一个整数 \(k=by+cx\),那很显然 \(k+x,k+2x,…,k+ax\) 都是合法的。包括 \(k\) 本身也是合法的。
那我们令函数 \(f(i)\) 为 \(by+cz \mod x=i\) 时最小的 \(by+cz\).。
那么我们可以发现其有一个性质:
证明很简单,\(f(i)\) 为最小的 \(by+cz\) 使得 \(by+cz \mod x=i\),那 又因为 \(by+cz+y \mod x=i+y\) 那么 \(f(i+y)=f(i)+y\) 而 \(i+y \geq (i+y) \mod x\),那么那么 \(f(i+y) \geq f((i+y) \mod x)\),那么就得证。
那很容易就可以发现这个式子与上面 \(dijkstra\) 松弛式子很像。那要求 \(f(i)\) 的最小值,不久相当于求 \(dis[i]\),因此我们可以用最短路的方法。先这样连边:
即:
- \(i\) 向 \((i+y) \mod x\) 连一条边权为 \(y\) 的边
- \(i\) 向 \((i+z) \mod x\) 连一条边权为 \(z\) 的边
那这样跑一遍 \(dijkstra\) 所求出的 \(dis[i]\) 就等于 \(f(i)\)
得出答案:
有了 \(f(i)\) 如何求出答案。
我们根据开局的性质 \(k=by+cz\) 那么要找的所有小于 \(h\) 的 \(ax+by+cz\) 共有 \(\lfloor \frac{h-k}{x} \rfloor\) 个。
为了使数量尽可能大,那 \(k\) 就要最小,也就等于 \(f(i)\),那最终答案就是:
加一是因为 \(f(i)\) 本身也合法。
完整代码:
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+10;
const int inf=9223372036854775807;
struct edge{
int v,w;
};
struct node{
int u,dis;
bool operator>(const node &a)const{return dis>a.dis;}
};
priority_queue<node,vector<node>,greater<node>>q;
vector<edge>e[N];
int dis[N],vis[N];
void dijkstra(){
dis[0]=0;
q.push({0,0});
while(!q.empty()){
int u=q.top().u;
q.pop();
if(vis[u])continue;
vis[u]=1;
for(auto ed:e[u]){
int v=ed.v,w=ed.w;
if(dis[v]>dis[u]+w){
dis[v]=dis[u]+w;
q.push({v,dis[v]});
}
}
}
}
signed main(){
int h,x,y,z;
scanf("%lld%lld%lld%lld",&h,&x,&y,&z);
h--;
for(int i=0;i<x;i++){
e[i].push_back({(i+y)%x,y});
e[i].push_back({(i+z)%x,z});
dis[i]=inf;
}
dijkstra();
int ans=0;
for(int i=0;i<x;i++){
if(h>=dis[i])ans+=(h-dis[i])/x+1;
}
printf("%lld",ans);
}
例题:
本文作者:XichenOC
本文链接:https://www.cnblogs.com/XichenOC/p/18688713
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步