同余最短路学习笔记

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

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

P3403 跳楼机

link

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

显然:如果一个数 \(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)+y\ge f((i+y) $ $ mod $ $ $ \(x)\)

$f(i)+z\ge f((i+z) $ $ mod $ $ $ \(x)\)

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

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

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

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

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

对于答案,其实就是:

$\sum_{i=0}^{x-1}\left \lfloor \frac{H-f(i)}{x} \right \rfloor +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\) 改成 \(l\)\(r\) 就好了,在最后统计答案的时候处理好闭区间就可以了。

贴上正常的代码。

我没有打这个正常的代码,从你谷上找了一篇题解直接贴过来了
#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(n^2)\) ,但是代码简短,简单易写。
(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 @ 2024-11-25 11:23  袍蚤  阅读(15)  评论(0)    收藏  举报