同余最短路其實是一種優化最短路建圖的方法。
通常是解決給定m個整數,求這m個整數能拼湊出多少的其他整數(這m個整數可以重復取)或給定m個整數,求這m個整數不能拼湊出的最小(最大)的整數。
我們通過一道例題來講解。
簡化一下題意:用a,b,c(這里用a,b,c來代替x,y,z)三個數能組成幾個小於h的整數。$h \leq 2^{63}-1$
因為h過大所以直接建圖顯然是不行的,我們要優化空間。
我們因為這個跳的順序是無關的,所以每個數都可以由若干次b/c再加上若干次a而形成的。
根據帶余除法我們知道所有的整數數都可以寫成ax+r的形式,其中a是除數,x是商而r是余數。
我們求出通過b/c操作能到達的最小的mod a余數是r的數,然后用一些算法即可求出能到達多少小於h的整數(到時再講)。
這時我們同余最短路就該排上用場了。這個最小即可表示成最短路。
我們可以讓a來做這個除數(其實應該用最小的最優),則r屬於$[0,a-1]$。
我們要求出所有到達所有r的最小值。所以對於每個r建立一個點。
它可以通過b,c到其它的數(點),所以我們對於每個點u連一條到v=(u+(b/c))%a的邊,長度為(b/c)。
現在從0開始跑最短路即可(初始化dis[0]=0)。
設余數r的最短路為dis[r],則可以到$\frac{h-dis[r]}{a}+1$個整數,統計答案。
#include <bits/stdc++.h> using namespace std; const long long MAXA = 1e5 + 10; struct node{ long long pre, to, val; }edge[MAXA * 20]; long long head[MAXA], tot; long long n, h; long long a[20]; long long dis[MAXA], vis[MAXA]; queue<long long> q; void add(long long u, long long v, long long l) { edge[++tot] = node{head[u], v, l}; head[u] = tot; } void spfa() { memset(dis, 0x3f, sizeof(dis)); dis[0] = 0; vis[0] = 1; q.push(0); while (!q.empty()) { long long x = q.front(); q.pop(); for (long long i = head[x]; i; i = edge[i].pre) { long long y = edge[i].to; if (dis[y] > dis[x] + edge[i].val) { dis[y] = dis[x] + edge[i].val; if (!vis[y]) { vis[y] = 1; q.push(y); } } } vis[x] = 0; } } long long solve(long long x) { long long ret = 0; for (long long i = 0; i < a[1]; i++) { if (dis[i] <= x) { ret += (x - dis[i]) / a[1] + 1; } } return ret; } int main() { n = 3; cin >> h; for (long long i = 1; i <= n; i++) { cin >> a[i]; } for (long long i = 0; i < a[1]; i++) { for (long long j = 2; j <= n; j++) { add(i, (i + a[j]) % a[1], a[j]); } } spfa(); cout << solve(h - 1);//他剛開始在1樓所以要-1 return 0; }
習題: