When a problem asks something like “Given integers, how many other integers can be formed from these integers (the integers may be used repeatedly)?”, “Given integers, find the smallest (largest) integer that these integers cannot form”, or “What is the minimum number of uses needed to form a number congruent to modulo ?”, the congruence shortest-path method can be used.
The congruence shortest-path method uses congruence to construct states, which can reduce the space complexity.
Similar to the difference constraints method, the states constructed using congruence can be regarded as vertices in a single-source shortest-path problem. The state transition in a congruence shortest-path problem is usually of the form , analogous to in a single-source shortest-path problem.
Examples
Example 1
P3403 Jumping Machine
Problem statement: Given , for , how many satisfy ?(,,)
Assume without loss of generality that .
Let be the lowest floor that can be reached using only Operation 2 and Operation 3 such that ; in other words, it is the smallest number congruent to modulo that can be obtained after applying Operation 2 and Operation 3, and it is used to calculate the number of valid integers in this congruence class.
This gives two transitions:
Usually, the smallest number among a set of is chosen as the modulus; here that is , so that the space complexity can be reduced as much as possible (by using the smallest residue system).
This is equivalent to performing the edge-construction operations in a shortest-path algorithm:
add(i, (i+y) % x, y)
add(i, (i+z) % x, z)
It remains only to find . A single shortest-path computation yields all the corresponding .
Implementation Based on Shortest Path
#include <iostream>
#include <queue>
using namespace std;
using ll = long long;
constexpr int MAXN = 100010;
constexpr ll linf = (1ull << 63) - 1;
ll h, x, y, z;
ll head[MAXN << 1], tot;
ll dis[MAXN], vis[MAXN];
queue<int> q;
struct edge {
ll to, next, w;
} e[MAXN << 1];
void add(ll u, ll v, ll w) {
e[++tot] = edge{v, head[u], w};
head[u] = tot;
}
void spfa() { // SPFA algorithm; see the shortest-path section
dis[0] = 0;
vis[0] = 1;
q.push(0);
while (!q.empty()) {
int u = q.front();
q.pop();
vis[u] = 0;
for (int i = head[u]; i; i = e[i].next) {
int v = e[i].to, w = e[i].w;
if (dis[v] > dis[u] + w) {
dis[v] = dis[u] + w;
if (!vis[v]) {
q.push(v);
vis[v] = 1;
}
}
}
}
}
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
cin >> h;
cin >> x >> y >> z;
if (x == 1 || y == 1 || z == 1) {
cout << h << '\n';
return 0;
}
--h;
for (int i = 0; i < x; i++) {
add(i, (i + z) % x, z);
add(i, (i + y) % x, y);
dis[i] = linf;
}
spfa();
ll ans = 0;
for (int i = 0; i < x; i++) {
if (h >= dis[i]) ans += (h - dis[i]) / x + 1;
}
cout << ans << '\n';
return 0;
}
In fact, it is not necessary to run a standard shortest-path algorithm. Notice two special properties:
First, there are only two edge weights, and for any path, the order in which the two edge weights are traversed has no effect because addition is commutative. Therefore, we can perform two shortest-path computations, constructing edges of only one weight each time.
Second, in a graph with only one edge weight, every vertex has one incoming edge (from ) and one outgoing edge (to ), so the entire graph must consist of several cycles. Moreover, it can be proved that there are cycles of equal length.
Proof
Let , and let , where .
Starting from and taking steps reaches . If this forms a cycle, then , which means .
Since , the smallest value is , so the cycle length is . Since the starting point can be arbitrary, all possible cycles have the same length, and the number of cycles is .
Furthermore, since all edge weights are positive, after going around a cycle twice, no further relaxation is possible. A single cyclic update pass is enough. This avoids being constrained by the complexity of a general shortest-path algorithm and achieves .
As with difference constraints, if is a solution, then is also a solution. Therefore, in this problem, take as the source vertex. At this source vertex, is the smallest value within the known range, so the resulting solution is also a smallest one.
The answer is:
The 1 is added because the floor containing is counted as well.
In the implementation, note that satisfies , so before solving the shortest-path problem, the initial value of should be at least . This exceeds the maximum value of long long in C++. Therefore, one can use unsigned long long, or first set and set the lowest floor to floor ; the rest of the code is unchanged.
Implementation with Cycle Optimization
#include <algorithm>
#include <climits>
#include <cstring>
#include <iostream>
#include <vector>
using ll = long long;
constexpr int maxn = 1e5 + 100;
ll d[maxn];
bool vis[maxn];
ll ans;
int gcd(int a, int b) { return b ? gcd(b, a % b) : a; }
void upd(int step, int M) {
int D = gcd(step, M);
int len = M / D;
for (int st = 0; st < D; st++) {
if (vis[st]) continue;
std::vector<int> v;
int u = st;
for (int i = 0; i < len; i++) {
v.push_back(u);
vis[u] = true;
u = (u + step) % M;
}
for (int r = 0; r < 2; r++) {
for (int i = 0; i < len; i++) {
int las = v[i];
int now = v[(i + 1) % len];
if (d[las] != LLONG_MAX) {
// If d[las] = LLONG_MAX, d[las] + step would overflow, so handle it separately
d[now] = std::min(d[now], d[las] + step);
}
}
}
}
}
int main() {
ll h;
std::cin >> h;
int x[3];
for (int i = 0; i < 3; i++) {
std::cin >> x[i];
}
std::sort(x, x + 3);
int M = x[0];
for (int i = 0; i < M; i++) {
d[i] = LLONG_MAX;
// h in this problem reaches the upper bound of ll, so if ll is used, the initial value must be set to LLONG_MAX
}
d[0] = 0;
upd(x[1], M);
memset(vis, 0, sizeof(vis));
upd(x[2], M);
ll H = h - 1;
ans = 0;
for (int i = 0; i < M; i++) {
if (d[i] <= H && d[i] != LLONG_MAX) {
ans += (H - d[i]) / M + 1;
}
}
printf("%lld\n", ans);
return 0;
}
Example 2
ARC084B Small Multiple
Problem statement: Given , find the digit sum of the multiple of with the smallest digit sum.()
This problem can be solved in time by using cyclic convolution to optimize the complete knapsack problem, but we want a linear-time algorithm.
Observe that any positive integer can be obtained by starting from and performing multiplication by and addition of in some order; the number of additions of is exactly the digit sum of the number. This suggests using shortest paths.
For every , add an edge of weight from to , and an edge of weight from to . (All vertex numbers are interpreted modulo .)
Every multiple of corresponds to a path from vertex to vertex in this graph, so it suffices to find the shortest path from to . Some paths are invalid (for example, they may contain consecutive edges of weight ), but the answers produced by such paths can never be better, so they do not affect the answer.
The time complexity is .