【算法学习】同余最短路
前言
给定 \(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;
}