洛谷题目:P2371 [CTSC 中国信息学国家集训队] 墨墨的等式 题解(本题难)
题目传送门:
P2371 [国家集训队] 墨墨的等式 - 洛谷 (luogu.com.cn)
前言:
这道题主要求我们计算在区间 中
能使等式
存在非负整数解,总体来难度的话还是挺大的,下面为大家详细讲解解题思路。
本题狠心思路:
我们采用同余最短路的方法来解决这个问题。同余最短路的狠心思想是利用余数的心智,将一个复杂的线性组合存在性问题转化为图论中的最短路问题,通过构件图并计算最短路径来确定满足条件的 的个数。
#具体步骤:
1、选择模数:
给定的 个系数
中选择一个非零的
作为模数 mod ,通常选择最小的非零
,作为模数可以使后续构建的图的节点数相对较少,减少 计算量。
2、构建图:
将所有可能的余数 0 到 看做图中的节点。对于每个
,从节点
向节点
连着一条长度为
的边。
3、求解最短路:
我们使用 Dijkstra 等最短路算法,以 0 为起点,计算到每个节点 的最短路路径的长度
。这里的
表示能得到余数为
的最小的
值。具体来说,从起点 0 开始,不断更新到其他节点的最短距离,当遍历完所有可能的边后,就能得到从 0 到每个余数节点的最短路径长度,当遍历完所有可能的边后后,就能得到从 0 到每个余数节点的最短路径长度。
4、统计答案:
对于给定的区间 ,遍历所有余数
。对于每个余数
,能的到余数为
的最小的
的值是
,那么满足
的非负整数
的个数就是该余数对应的满足条件 的个数。我们可以通过计算满足不等式
的
的范围来得到这个个数。具体计算的时候,我们要先计算
的下线
和上限
,则满足条件的
的个数为
,将所有余数对应的个数累脚起来就是最答案。
##示例解释:
假设我们输入 。
选择 作为模数。
构建图:
从节点 0 向节点 连长度为 3 的边,向节点
连长度为 5 的边。
从节点 1 向节点 连长度为 3 的边,向节点
连长度 5 的边。
从节点 2 向节点 连长度为 3 的边,向节点
连长度为 5 的边。
使用 Dijkstra 算法计算最短路,得到 。
统计答案:
对于 ;
计算 ,满足条件的
的有 3 - 2 + 1 = 2 个,对应的
为 2 * 3 = 6 和 3 * 3 = 9. 。
对于 ;
计算 ,满足条件的 k 有 1 - 0 + 1 = 2个,对应的 b 为 5 + 0 * 3 = 5 和 5 + 1 * 3 = 8.
对于 ;
计算 ,满足条件的 k 有 2 - 1 + 1 =2 个,但其中 b = 3 + 1 * 3 =6 和 b = 3 + 2 * 3 = 9 与前面重复,只新增 1 个不重复的 b 。
最终满足条件的 b 的个数为 5 。
###复杂度:
1、时间复杂度:
。
2、空间复杂度:
。
####代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MAX = 15;
const int MAN = 5e5 + 5;
struct Node {
LL dis;
int u;
Node(LL _dis, int _u) : dis(_dis), u(_u) {}
bool operator>(const Node& other) const {
return dis > other.dis;
}
};
int n;
LL l, r;
int a[MAX];
LL dis[MAN];
// 计算区间 [l, r] 内满足条件的 b 的数量
LL c(LL l, LL r, LL base, LL mod) {
if (base > r) return 0;
LL left = max(0LL, (l - base + mod - 1) / mod);
LL right = (r - base) / mod;
return max(0LL, right - left + 1);
}
int main() {
cin >> n >> l >> r;
for (int i = 0; i < n; ++i) {
cin >> a[i];
}
// 找到最小的非零 a[i] 作为模数
int mod = 0;
for (int i = 0; i < n; ++i) {
if (a[i] > 0 && (mod == 0 || a[i] < mod)) {
mod = a[i];
}
}
if (mod == 0) {
cout << (l == 0 && r == 0 ? 1 : 0) << endl;
return 0;
}
// 初始化距离数组
fill(dis, dis + mod, LLONG_MAX);
dis[0] = 0;
// 使用优先队列进行 Dijkstra 算法
priority_queue<Node, vector<Node>, greater<Node>> pq;
pq.push(Node(0, 0));
while (!pq.empty()) {
Node cur = pq.top();
pq.pop();
LL d = cur.dis;
int u = cur.u;
if (d > dis[u]) continue;
for (int i = 0; i < n; ++i) {
if (a[i] == 0) continue;
int v = (u + a[i]) % mod;
LL N = d + a[i];
if (N < dis[v]) {
dis[v] = N;
pq.push(Node(N, v));
}
}
}
// 统计满足条件的 b 的数量
LL ans = 0;
for (int i = 0; i < mod; ++i) {
ans += c(l, r, dis[i], mod);
}
cout << ans << endl;
return 0;
}