【算法学习】同余最短路

前言

给定 \(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\) 也互不相同。

image

所以我们有 \(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\))。

那么最后的答案就是:

\[\sum_{i=0}^{x-1} \lfloor \frac{h-dis_i}{x} \rfloor+1 \]

代码部分:

#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;
}
posted @ 2024-11-04 08:01  sad_lin  阅读(3)  评论(0编辑  收藏  举报