同余最短路学习笔记

今天闲着没事去翻图论如何建模,看到差分约束下面讲的就是我没学过的同余最短路,对此感到极为惊奇,特此来学习。

这个东西看着就很抽象,直接来看题。

P3403 跳楼机

link

形式化题意:给定四个正整数 x,y,z,H,求整数 d 满足
d[0,H],ax+by+cz=dd 的个数。

显然:如果一个数 k 可以被写为 k=bx+cy(此处 b,c 都为非负整数),则 k 为一定为合法答案,同时 k+x,k+2x,k+3x,k+4x 等都一定为合法答案。(此处保证答案小于等于 H

我们可以定义一个函数为 f(i),表示 (by+ cz) mod x = i 时取的最小的 ay+bz

我们可以通过ZLC惊人的注意力显然发现性质:

f(i)+yf((i+y) mod x)

f(i)+zf((i+z) mod x)

我们可以再次用ZLC惊人的注意力显然发现性质:这玩意长得好像图论里面的四边形不等式哇哇哇!!!

所以可以掏出图论,开始连边:(i[0,x)

i(i+y) mod x 连一条边权为 y 的有向边。

i(i+z) mod x 连一条边权为 z 的有向边。

连完边之后,跑一遍最短路(迪杰斯特拉和那死了的玩意都可以),得到 f[i] 。(其实就是得到的 dis[i]

对于答案,其实就是:

i=0x1Hf(i)x+1

为什么要加 1,那是因为 f(i) 自己也是可行解。

对于这道题,只要把 H 减个 1 ,就是上面给的所有了。

#include<bits/stdc++.h>
#define int long long
#define pb push_back
#define pi pair<int,int>

using namespace std;

const int N=1e5+100;
const int INF=(1ll<<63)-1;

int h,d[N],ans;
int x,y,z;
bool v[N];

vector<pi> G[N];
priority_queue<pi> q;

signed main()
{
	cin>>h>>x>>y>>z;
	h--;	
	for(int i=0;i<x;i++)	
		G[i].pb({(i+y)%x,y}),G[i].pb({(i+z)%x,z}),d[i]=INF;
	d[0]=0;
	q.push({0,0});
	while(q.size())
	{
		int x=q.top().second;q.pop();
		if(v[x])	continue;
		v[x]=true;
		for(auto k:G[x])
		{
			int y=k.first,z=k.second;
			if(d[y]>d[x]+z)
				d[y]=d[x]+z,q.push({-d[y],y});
		}
	}
	for(int i=0;i<x;i++)	
		if(h>=d[i])
			ans+=(h-d[i])/x+1;
	cout<<ans<<endl;
	return 0;
} 

P2371 墨墨的等式

link

这道题就是上一道题的拓展版。上一道题只有三个数,这一道题就是 n 个数。

和上一道题一样的思路,只需要把 H 改成 lr 就好了,在最后统计答案的时候处理好闭区间就可以了。

贴上正常的代码。

我没有打这个正常的代码,从你谷上找了一篇题解直接贴过来了
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 5e6 + 20;

int n;
ll w[15];
ll h,ans;
ll d[maxn];
ll l,r; 
bool v[maxn];


struct N{
	int to,net,va;
}a[maxn << 2];
int head[maxn];
int cnt;

void add(int x,int y,int w)
{
	a[++cnt].to = y;
	a[cnt].net = head[x];
	a[cnt].va = w;
	head[x] = cnt;
}

void SPFA()
{
	memset(v,0,sizeof v);
	memset(d,0x3f3f3f3f,sizeof d);
	queue<int> q;
	q.push(0);
	v[0] = 1;
	d[0] = 0;// 注意从0开始,因为基数为 0,换句话说就是,余数从0开始算 
	while(!q.empty())
	{
		int from = q.front(); q.pop();
		v[from] = 0;
		for(int i = head[from]; i; i = a[i].net)
		{
			int to = a[i].to;
			int va = a[i].va;
			if(d[to] > d[from] + va)
			{
				d[to] = d[from] + va;
				if(!v[to])
				{
					v[to] = 1;
					q.push(to);
				}
			}
		}
	}
}



int main()
{
	scanf("%d%lld%lld",&n,&l,&r);
	for(int i = 1; i <= n; ++i)	scanf("%lld",&w[i]);
	sort(w+1,w+n+1);
	for(ll i = 0; i < w[1]; ++i)
	{
		for(int j = 2; j <= n; ++j)
		{
			add(i, (i+w[j]) % w[1] , w[j]);
		}
	}
	SPFA();
	for(ll i = 0; i < w[1]; ++i)
	{
		if(d[i] <= r) // 如果小于r ,就加答案 
		{
			ans += (r - d[i])/w[1] + 1;
		}
		if(d[i] < l) // 如果小于l ,说明不合法,减去这一部分 
		{
			ans -= (l - 1 - d[i])/w[1] + 1;
		}
	}
	printf("%lld\n",ans);
	return 0;
}

然后我看到了魏老师的 blog。膜拜得无以复加。

简要内容:存在简单的,不需要最短路的做法。

选择不从图论的角度去思考,而是选择Dp。更具体的说,完全背包。再具体一点说,模 m 意义下的完全背包。

具体内容可以看魏老师的 blog

总之,对于这种做法,时间复杂度为 O(n2) ,但是代码简短,简单易写。
(ps:魏老师的代码好像没有判 0 诶)

代码

#include<bits/stdc++.h>
#define int long long

using namespace std;

const int N=5e5+100;

int n,m;
int l,r,ans;
int a[N];
int f[N];
bool sg=false;

signed main()
{
	cin>>n>>l>>r;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];	
		if(!a[i])
			sg=true;
	}
	
	if(sg)
	{
		cout<<r-l<<endl;
		exit(0);
	}
	
	memset(f,0x3f,sizeof f);
	f[0]=0;
	
	sort(a+1,a+n+1);
	m=a[1];
	
	for(int i=2;i<=n;i++)
		for(int j=0,lim=__gcd(m,a[i]);j<lim;j++)
			for(int t=j,c=0;c<2;c+=t==j)
			{
				int p=(t+a[i])%m;
				f[p]=min(f[p],f[t]+a[i]);
				t=p;
			}
			
	for(int i=0;i<a[1];i++)
	{
		if(r>=f[i])	ans+=max((int)0,(r-f[i])/a[1]+1);
		if(l>f[i])	ans-=max((int)0,(l-1-f[i])/a[1]+1); 
	}		
	
	cout<<ans<<endl;
	
	return 0;
}

一些其他题目:

P2662 牛场围栏:一定要注意判无解啊啊啊

[ABC077D] Small Multiple:好题啊啊啊啊

posted @   袍蚤  阅读(10)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示