【算法学习】同余最短路
前言
给定 \(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
直接从数位和上考虑,从 \(1\) 开始,加 \(1\) 数位和加 \(1\),乘 \(10\) 数位和不变,直到若干次操作后,该数为 \(k\) 的倍数,此时当前的操作数位加和就为数位和。
此时就是同余最短路,首先加 \(1\) 操作就是向下一个点连权值为 \(1\) 的边,乘 \(10\) 操作就是向 \((x\times 10)\mod k\) 连权值为 \(0\) 的边,以 \(1\) 为起点 \(dis_1=1\),跑最短路,\(dis_0\) 即为最小权值和。
#include<bits/stdc++.h>
#define ll long long
#define int ll
#define ls t[p].l
#define rs t[p].r
#define re register
#define pb push_back
#define pir pair<int,int>
#define f(a,x,i) for(int i=a;i<=x;i++)
#define fr(a,x,i) for(int i=a;i>=x;i--)
#define lowbit(x) x&-x;
using namespace std;
const int N=3e5+10;
const int M=2e5+4;
const int mod=1e9+7;
const int INF=1e17+7;
mt19937 rnd(251);
int n;
struct ss{
int v,w,next;
}a[N];
int head[N];
int cnt=0;
int b[N];
void add(int u,int v,int w){
a[++cnt].v=v;
a[cnt].w=w;
a[cnt].next=head[u];
head[u]=cnt;
}
int dis[N];
struct sss{
int w,id;
bool operator()(const sss& g,const sss& h) const {
return g.w>h.w;
}
};
priority_queue<sss,vector<sss>,sss> q;
void dijkstra(){
dis[1]=1;
q.push({1,1});
while(!q.empty()){
sss k=q.top();
q.pop();
int x=k.id;
if(k.w>dis[x]) continue;
for(int i=head[x];i;i=a[i].next){
int y=a[i].v;
if(dis[y]>dis[x]+a[i].w){
dis[y]=dis[x]+a[i].w;
q.push({dis[y],y});
}
}
}
}
int l,r;
void solve(){
cin>>n;
dis[0]=INF;
for(int i=1;i<n;i++){
dis[i]=INF;
add(i,(i+1)%n,1);
add(i,(i*10)%n,0);
}
dijkstra();
cout<<dis[0]<<"\n";
}
signed main(){
// freopen("a.in","r",stdin);
// freopen("a.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(nullptr);
int t=1;
// cin>>t;
while(t--){
solve();
}
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;
}

浙公网安备 33010602011771号