算法学习笔记 同余最短路

问题背景

给出 n 个整数,每个整数可以取任意多次,询问关于它们能拼凑出的数的一些信息

即用于求解形如“给出 n 个数 a1,a2,...,an ,求有多少个正整数 b 满足 b≤T 能使得 ∑i=1i=naixi=b 存在非负整数解”一类的问题。

首先先看一道例题 跳楼机

大致题意,你现在有一个数 h ,初始值为 1 。现在你可以进行以下四种操作若干次:

  1. 使 h=h+a
  2. 使 h=h+b
  3. 使 h=h+c
  4. 使 h=1

实际上就是问你对于给定的 a,b,c ,存在多少的数字 k 使得 ax+by+cz=k ,其中 0<=k<=H (为了方便起见,下述均将起点视为0,即整体往下移动了一位)

对于上述等式,我们考虑到,如果 k 是存在的,即 ax+by+cz=k ,那么对于我们可以想到,下面等式a(x+p)+bx+cy=k+ap( p 是一个非负整数)也是成立的,然后我们发现 k≡(k+ap)moda ,也就是说只要我们能凑出 k ,那么只需满足在范围内, k+a,k+2a,...,k+pa 都是可以凑出来的,因为x是任意的,对于同余我们有如下,记 q=kmoda ,那么只需要对于所有满足 q=pmoda( ,p,k 模 a 的余数相同),并且 p\epsilon [k,H](题目范围)的正整数 p 都是合法的,其中 p 的个数就应该是 [(H−k)/a]+1 ,至于为什么是这个我们举个例子,假设 p 可取的值为 [k,k+a,k+2a,H) ( H 是上界,表示取不到),那么我们可取的数字就是三个,便是上式,加1是因为端点值 k 也是需要算上的。

了解了这里之后我们便有如下思考,既然找到一个 k ,那他后面可以凑出来的数字我们就知道了,不需要再去计算出来具体的了,于是我们想到要是 k 最小就好了,这样我们可以省去很多计算,直接带入上述公式就可以了。

那么更一般的,设d_i 表示满足 k≡i(moda) 的最小k ,于是可以凑出 i 的个数便是 [(H−di)/a]+1 ,那么如果我们能计算出所有的 did_0,d_1,....,d_{a-1} 那我们的答案就是 \sum_{i=0}^{i=a-1}[(H-d_i)/a]+1 (d _i ​≤H)

那么我们现在的问题就是如何来求解 d_0,d_1,....,d_{a-1} 

以上述题目为例,假设我们现在考虑到的某个数为 k , p=kmoda

  • 若使用了一次 k=k+a ,那么 (k+a)moda=kmoda 余数不变,仍为 p 。
  • 若使用了一次 k=k+b ,那么余数 p=(k+b)moda=(p+b)moda 。
  • 若使用了一次 k=k+c ,那么余数 p=(k+c)moda=(p+c)moda 。

考虑到状态转移

  • 若使用了一次 k=k+a,那么本次的 dp+a=dp 没有任何变化,因为p+a取模还是p的倍数。
  • 若使用了一次 k=k+b ,那么有 dp​⟶(b)​dp+bmoda 。
  • 若使用了一次 k=k+c ,那么有 dp​⟶(c)​dp+cmoda。

于是我们便找到了所有的 di ,接下来就是如何求解,考虑到与之类似的形式并且又要求出最小的 di ,于是我们最短路中的 disv=disu+wi ,其中 wi 表示从u到v的一段距离,对比上述式子,我们可以把 [0,a−1] 范围内的数都抽象为点( moda 的余数),每一次转移都抽象为边,从而我们得到下述求解过程:

  • d0​=0 ,即视作 0 为源点。
  • 初始化 d1,d2,da−1 为无穷大。
  • 连接一条形如 x⟶(b)​(x+b)moda 的边。
  • 连接一条形如 x⟶(c)(x+c)moda 的边。

接下来跑一遍最短路算法,便可以得到小的 di 即满足 di​≡i(moda) 并且最小的数

#include<bits/stdc++.h>
using namespace std;
#define int long long
typedef pair<int,int>PII;
const int N=1e5+10;
int h[N],e[N*2],ne[N*2],w[N*2],idx;
int dis[N],st[N];
int H,x,y,z;

void add(int a,int b,int c)
{
	e[++idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx;
}

void dij()//dijkstra
{
	memset(dis,0x3f,sizeof dis);
	priority_queue<PII,vector<PII>,greater<PII>>q;
	dis[1]=1;q.push({1,1});
	while(q.size())
	{
		int u=q.top().second,d=q.top().first;q.pop();
		if(st[u])continue;
		st[u]=1;
		for(int i=h[u];i;i=ne[i])
		{
			int v=e[i];
			if(dis[v]>d+w[i])
			{
				dis[v]=d+w[i];
				q.push({dis[v],v});
			}
		}
	}
}
signed main()
{
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>H>>x>>y>>z;
	if(x==1||y==1||z==1)//只要有一个为1我们便可以到达任意层
	{
		cout<<H;return 0;
	}	
	for(int i=0;i<x;i++)
	{
		add(i,(i+y)%x,y),add(i,(i+z)%x,z);//建立边
	}
	dij();
	int ans=0;
	for(int i=0;i<x;i++)
	{
		if(dis[i]<=H)
			ans+=(H-dis[i])/x+1;//推出的等式
	}
	cout<<ans;
	return 0;
}

  • 40
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值