HYSBZ2118 墨墨的等式(最短路径)

本文介绍了一种利用最短路径算法解决特定数学问题的方法。该问题要求找出一系列数值经过特定运算后能覆盖某个区间内所有可能值的数量。通过寻找序列中的最小值并进行取模运算,构建图模型并运用SPFA算法求解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这题是校周赛上碰到的,很经典的一道题,里面的思想很巧妙。

题意:

中文题不解释

要点:

一开始根本想不到是最短路径,我们先把{an}中最小的值找到为a1,这样将所有的an%a1,这样最后的结果就是相当于x+k*a1,那么我们就要求这个x,也就是所有an%a1后组合可以达到的数。可以简单的看出x<a1,这样我们就将0~a1-1建图对应x,边权就是a[i],每次增加边的值后和%a1,最后就转换为一个最短路径问题,用spfa搞定,最后dis[i]就对应x,最后求和就通过(l-dis[i])/a1求出k,两个区间求出的k相减就是区间内部可能有的个数。

#include<iostream>
#include<cstring>
#include<string>
#include<queue>
#include<vector>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N = 500500;
const ll INF = 1000000000000000LL;
ll dis[N];
int a[25],mina,n;
int vis[N];

void spfa()
{
	queue<int> q;
	memset(vis, 0, sizeof(vis));
	for (int i = 0; i <= mina; i++)
		dis[i] = INF;
	int S = 0, T = mina;
	q.push(S); vis[S] = 1; dis[S] = 0;
	while (!q.empty())
	{
		int u = q.front(); q.pop();
		vis[u] = 0;
		for (int i = 0; i < n; i++)
		{
			int v = (u + a[i]) % mina;
			if (dis[v] > dis[u] + a[i])
			{
				dis[v] = dis[u] + a[i];
				if (!vis[v])
				{
					q.push(v);
					vis[v] = 1;
				}
			}
		}
	}
}

int main()
{
	ll l, r;
	scanf("%d%lld%lld", &n, &l, &r);
	mina = N;
	for (int i = 0; i < n; i++)
	{
		scanf("%d", &a[i]);
		mina = min(a[i], mina);
	}
	spfa();
	ll x, y, ans = 0;
	for (int i = 0; i < mina; i++)
		if (dis[i] <= r)
		{
			x = max(0LL, (l - dis[i]) / mina);
			y = (r - dis[i]) / mina;
			if (x*mina + dis[i] < l) x++;
			if (y*mina + dis[i] > r) y--;
			ans += y - x + 1;
		}
	printf("%lld\n", ans);
	return 0;
}


转载于:https://www.cnblogs.com/seasonal/p/10343653.html

<think>我们正在讨论同余最短路算法以及其在解决“等式”问题中的应用。下面我将详细解释同余最短路算法的原理,并结合例题进行说明。##同余最短路算法同余最短路是一种用于解决特定类型整数规划问题的算法。它常用于求解形如“用若干个非负整数的线性组合能拼出多少个在某个区间内的数”的问题。例如,给定正整数序列$a_1,a_2,\dots,a_n$以及范围$[L,R]$,要求用非负整数组合$\sum_{i=1}^{n}k_ia_i$($k_i$为非负整数)所能拼出的在$[L,R]$内的数的个数。###基本思想1.**选取模数**:在数列中选取最小的一个数(一般取最小值,记为$minv$)作为模数。这是因为在模$minv$意义下,每个数可以表示为若干个$minv$加上余数。2.**状态定义**:定义$d(i)$表示能够通过非负整数组合拼出的数中模$minv$余$i$的最小数($i$取$0$到$minv-1$)。3.**最短路建图**:我们建立$minv$个点,分别代表模$minv$的余数$0,1,\dots,minv-1$。对于每个点$i$,对于每一个其他数$a_j$($a_j\neqminv$),从$i$向$(i+a_j)\modminv$连一条边权为$a_j$的边(实际上,边权并不直接用于最短路计算中的相加,而是用于更新代价,具体见下)。但注意,我们通常用松弛操作:$d[(i+a_j)\modminv]=\min\{d[(i+a_j)\modminv],d[i]+a_j\}$。4.**跑最短路**:初始时$d(0)=0$,因为0模$minv$为0,而且0是可以通过不取任何数得到的。然后,从0出发,我们跑一个最短路(通常使用SPFA或Dijkstra),更新所有点的$d$值。5.**统计答案**:对于区间$[L,R]$,模$minv$余$i$的数中,最小的能被拼出的数是$d(i)$。那么,对于余数为$i$的数,所有大于等于$d(i)$的且模$minv$余$i$的数都能被拼出。因此,对于区间$[1,R]$,模$minv$余$i$的能拼出的数的个数为:$\max(0,(R-d(i))/minv+1$(如果$d(i)\leqR$)。注意,当$d(i)$超过$R$时,则余数为$i$的数在区间内不存在可拼出的数。同理计算$[1,L-1]$,然后二者相减即可得到$[L,R]$内满足的数的个数。###例题:等式**题目描述**:突然对等式很感兴趣,他正在进行一个有关等式的实验。他面前有一个由正整数组成的序列$a_1,a_2,\dots,a_n$($n\leq12$,$a_i\leq5\times10^5$)。他想知道对于区间$[L,R]$($L,R\leq10^{12}$)中的哪些整数$x$,可以用非负整数组合$\sum_{i=1}^{n}k_ia_i$等于$x$(即存在$k_i\geq0$使得等式成立)。**解题思路**:使用同余最短路算法。取$minv=\min(a_i)$(非0的最小值,通常先去除0?但题目为正整数,所以没有0)。然后构建图,模$minv$,跑最短路得到每个余数的最小$d(i)$。然后统计$[0,R]$(注意包括0)可拼出的数的个数(因为题目区间为$[L,R]$,且非负整数组合包括0,但注意0是恒成立的,所以在区间内如果要求正数则要扣除0,但题目问的是等式,x为非负整数,所以0也算,不过题目区间是[L,R],所以直接算区间)。**具体步骤**:1.取最小的非零$a_i$作为模数$mod$(假设为$a_0$)。2.建立$mod$个点,对应模$mod$的余数$0$到$mod-1$。3.初始化$d$:$d[0]=0$,其他$d[i]=+\infty$(表示不可达)。4.对于每一个余数$i$($0\lei< mod$),对于每一个$a_j$($j=0$到$n-1$),执行松弛操作:令$v=(i+a_j)\%mod$,$w=d[i]+a_j$,如果$w< d[v]$,则更新$d[v]=w$。(注意:实际上,这个松弛操作可以理解为:从$i$点通过加一个$a_j$,到达$v$点,花费增加了$a_j$,所以更新$v$点的最小花费)5.使用SPFA或Dijkstra(这里边权为正,所以Dijkstra更稳定)跑最短路。6.对于区间$[L,R]$,统计能拼出的数的个数:-对于余数$r$($0\ler< mod$):-如果$d[r]\leqR$,则余数为$r$的数在区间$[d[r],R]$中满足的条件数的个数为:$\left\lfloor\frac{R-d[r]}{mod}\right\rfloor+1$。-如果$d[r]< L$,则在区间$[0,L-1]$中,余数$r$的数个数为$\left\lfloor\frac{L-1-d[r]}{mod}\right\rfloor+1$(注意:要求$d[r]\leqL-1$时);否则,若$d[r]\geL$,则在$[0,L-1]$中该余数没有数。-实际上,我们可以分别计算$[0,R]$和$[0,L-1]$中满足条件的数的个数,然后相减。7.计算总数:$ans=f(R)-f(L-1)$,其中$f(x)$表示区间$[0,x]$中能拼出的数的个数。即:$$f(x)=\sum_{r=0}^{mod-1}\left[d[r]\lex\right]\cdot\left(\left\lfloor\frac{x-d[r]}{mod}\right\rfloor+1\right)$$**注意**:在松弛过程中,由于初始点0已经可以拼出0,所以$d[0]=0$,然后通过加上$a_j$来扩展。另外,在图中可能存在多个环,但是因为边权为正,所以用Dijkstra不会产生负环问题。###代码框架```pythonimportheapqdefsolve(a,L,R):#假设a中的元素都是正整数mod=min(a)#选取最小的数作为模数#如果模为0?题目保证正整数,所以最小至少为1#初始化距离数组d=[10**18]*modd[0]=0#使用堆优化的Dijkstraheap=[(0,0)]#(dist,node)whileheap:dist,node=heapq.heappop(heap)ifdist!=d[node]:continuefornumina:nxt=(node+num)%modndist=dist+numifndist< d[nxt]:d[nxt]=ndistheapq.heappush(heap,(ndist,nxt))#计算f(x)=sum_{r}[d[r]<=x]*((x-d[r])//mod+1)deff(x):res=0forrinrange(mod):ifd[r]<=x:res+=(x-d[r])//mod+1returnresans=f(R)-f(L-1)returnans```###注意事项-由于$L,R$很大($10^{12}$),所以需要保证算法效率,点数为$mod$,而$mod$最大为$a_i$的最小值,由于$a_i\leq5\times10^5$,所以点数最大50万,边数是50万*12(每个点连12条边)所以总边数600万,使用Dijkstra堆优化可以承受(时间复杂度$O(n\logn)$,其中$n$是点数,即$mod$,最大50万,堆优化为$O(n\logn)$,600万条边,这个复杂度是可行的)。-在计算$f(x)$时,遍历所有余数,所以复杂度$O(mod)$,可接受。-初始化距离数组时,注意设一个足够大的数(大于可能的最大值,最大可能值不超过$mod*10^{12}$?实际上我们的距离值可能达到$10^{12}$量级,因为松弛的时候加的数最大$5e5$,而迭代次数可能很多,所以初始值要大于可能的最大值(最大可能是很多个$5e5$相加,但由于我们是在模意义下跳,所以实际上不会无限加,但最大可能达到$10^{12}$量级)。所以我们设初始值$10^{18}$(因为$R$最大$10^{12}$,所以$d$最大有可能超过$10^{12}$,但不能超过$10^{18}$,因为$5e5$个点,每个点最多被松弛一次,每次增加的数不超过$5e5$,所以最大$5e5*5e5=2.5e11$,所以初始值设为$10^{15}$就足够?但为了安全,设为$10^{18}$)。###其他说明同余最短路算法将原问题转化为一个在有限状态(模$minv$的剩余系)上的最短路问题,从而有效解决了在极大范围内的计数问题。这种方法的关键在于抓住模意义下的余数,利用最小能表示的余数去覆盖更大的区间。##相关问题1.同余最短路算法中如果模数为0(即最小值为0)怎么处理?2.在等式中,如果存在负数怎么处理?3.除了Dijkstra算法,还可以使用什么算法求解同余最短路?4.如何证明同余最短路算法的正确性?</think> ### 同余最短路算法详解及「等式」应用 #### **算法核心思想** 同余最短路是一种用于求解**非负整数线性组合可行解**的高效算法,特别适合处理形如$x=\sum_{i=1}^{n} k_ia_i$($k_i \geq 0$)的计数问题。其核心是将等式转化为**模意义下的最短路问题**,通过选取模数将无限整数域映射到有限剩余系。 **算法步骤**: 1. **选取模数**:取最小非零系数$a_{\min} = \min\{a_1, a_2, ..., a_n\}$作为模数 2. **建图**: - 建立$a_{\min}$个节点,分别代表余数$0,1,...,a_{\min}-1$ - 对每个节点$r$和每个系数$a_i$,连边$r \to (r+a_i) \bmod a_{\min}$,边权为$a_i$ 3. **跑最短路**:以余数0为起点($x=0$作为边界),计算到每个余数$r$的最小路径值$d[r]$ - $d[r] = \min\{x \mid x \equiv r \pmod{a_{\min}}, x=\sum k_ia_i\}$ 4. **统计解数量**:对区间$[L,R]$内可解的$x$,满足$x \geq d[r]$且$x \equiv r \pmod{a_{\min}}$ --- ### **「等式」例题解析** **题目描述**:给定正整数序列$a_1,\cdots,a_n$和区间$[L,R]$,求能用$x=\sum k_ia_i$($k_i \geq 0$)表示的$x \in [L,R]$的数量。 #### **解题思路** 1. **模型转化** 令$B = \min\{a_i\}$(默认$a_i>0$),对$x \in \mathbb{Z}^+$,存在: $$ x = \color{blue}{d[r]} + \color{red}{kB} \quad (r=x \bmod B,\ k \geq 0) $$ 其中$\color{blue}{d[r]}$是模$B$余$r$的最小可行解。 2. **建图与最短路** ```python import heapq def dijkstra(a, base): dist = [10**18] * base dist[0] = 0 heap = [(0, 0)] while heap: d, r = heapq.heappop(heap) if d != dist[r]: continue for ai in a: # 松弛操作:尝试扩展余数(r+ai)%base nr = (r + ai) % base nd = d + ai if nd < dist[nr]: dist[nr] = nd heapq.heappush(heap, (nd, nr)) return dist ``` 3. **区间计数公式** 对每个余数$r$,计算区间内满足条件的$x$数量: $$ \text{count}_r = \max\left(0, \left\lfloor \frac{R - d[r]}{B} \right\rfloor + 1 \right) - \max\left(0, \left\lfloor \frac{L-1 - d[r]}{B} \right\rfloor + 1 \right) $$ 总数量为$\sum_{r=0}^{B-1} \text{count}_r$ #### **复杂度分析** - **时间复杂度**:$O(B \log B + nB)$(Dijkstra堆优化) - **空间复杂度**:$O(B)$ 其中$B=\min\{a_i\}$,通常$B \ll L,R$(如$R \leq 10^{12}, B \leq 5 \times 10^5$) #### **关键性质** 1. **可行性充要条件**: $\forall x \geq d[r]$且$x \equiv r \pmod{B}$,$x$均可被表示 2. **下界优化**: 当$d[r] \leq R$时,解数量由$\left\lfloor \frac{R-d[r]}{B} \right\rfloor + 1$决定 --- ### **算法关联与应用本质** 同余最短路与差分约束系统(如引用[1])本质相同: - **差分约束**:$x_j - x_i \leq a_k$ → 边$i \xrightarrow{a_k} j$ - **同余最短路**:$x \geq d[r] + kB$ → 余数跳变$r \xrightarrow{a_i} (r+a_i)\%B$ 两者均通过最短路确定**变量的边界值**,以不等式/等式形式描述约束关系。 **扩展应用**:硬币系统、跳表问题、线性丢番图方程非负解计数等。 --- ### 相关问题 1. 同余最短路中如何处理$a_i=0$的情况? 2. 当$R$极大(如$10^{18}$)时如何优化空间? 3. 该算法与Bellman-Ford算法的负环检测有何关联? 4. 是否存在无法用同余最短路求解的非负线性组合问题? [^1]: 差分约束系统与最短路关联性分析 [^2]: Floyd算法在多源最短路中的应用
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值