同余最短路
- 能解决的问题: 给定 n n n个整数,求这 n n n个数能拼凑出多少其他的整数(可重复取)。或者求 n n n个整数不能拼出的最大/最小数。
这类题目较少,主要的就这两道题,看例题吧
P3403 跳楼机
-
若暴力: O ( h ) O(h) O(h)
-
若动态规划:完全背包
-
当 h h h到达 l o n g l o n g long\;long longlong级别时,就不好用了,只能用同余最短路
-
咋做:
-
先用同余思想将 O ( h ) O(h) O(h)复杂度降到 O ( m i n { a } ) O(min\{a\}) O(min{a}): 发现对于其中某一个 a i a_i ai(这里面指的是 x , y , z x,y,z x,y,z中的一个),我们考虑剩余 n − 1 n-1 n−1个数求和可以组成的数 A = ∑ j = 1 , j ≠ i n − 1 k j a j A=\sum\limits_{j=1,j\ne i}^{n-1}k_ja_j A=j=1,j=i∑n−1kjaj。这个数若再加上 a i a_i ai,那么其组成的数的个数在 [ 1 , h ] [1,h] [1,h]范围内就有 ( h − A ) / a i + 1 (h-A)/a_i+1 (h−A)/ai+1个。
我们想这些数太过庞大而且有重复,那可不可以只考虑 A A A,有一个 A A A就有一堆个数了呢?考虑两个数 A p , A q A_p,A_q Ap,Aq,若 A P m o d a i = A q m o d a i A_P\bmod a_i=A_q\bmod a_i APmodai=Aqmodai,那么他俩统计的个数表示的数便是重复的,于是发现任意两个模 a i a_i ai相同的 A A A都会重复,那我们只找模 a i a_i ai不同的 a i a_i ai个数( 0 0 0到 a i − 1 a_i-1 ai−1,就是剩余系)不就可以了嘛。
-
和最短路的关系: 在找一个数由其他 a j ( j ≠ i ) a_j(j\ne i) aj(j=i)组成时,为了减少重复且大的数能由 A k A_k Ak推出,我们只需要找最小的 A k A_k Ak使 A k m o d a i = k A_k\bmod a_i=k Akmodai=k即可。
我们设 f ( k ) f(k) f(k)表示在 a i a_i ai的剩余系内到达的 A k A_k Ak,由动态规划思想发现
f ( ( k + a j ) % a i ) = min { f ( k ) + a j } f((k+a_j)\%a_i)=\min\{f(k)+a_j\} f((k+aj)%ai)=min{f(k)+aj}
化作最短路就表示一个节点 f ( k ) f(k) f(k)到另一个节点 f ( ( k + a j ) % a i ) f((k+a_j)\%a_i) f((k+aj)%ai)的一条边权为 a j a_j aj的路径,而且求的是最小值(最短路)。 -
最后用最短路算法跑一遍,将所有代表 a i a_i ai的剩余系的节点的最小值算出就行了
-
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i <= (b); i++)
#define per(i, a, b) for(int i = (a); i >= (b); i--)
#define ll long long
#define db double
#define VI vector<int>
#define pi acos(-1.0)
#define PLI pair<ll, int>
const int INF = 0x7fffffff;
const int N = 1e5 + 5;
const db eps = 1e-10;
using namespace std;
int a[5];
bool vis[N];
ll ans = 0, h, dist[N]; //dist[i]表示仅通过操作2和操作3能到达的 mod x == i 的最小楼层: **可能为 ll**
void dij(int k){
priority_queue<PLI, vector<PLI>, greater<PLI> > q;
memset(dist, 0x3f, sizeof(dist));
memset(vis, 0, sizeof(vis));
dist[k] = 1, q.push({dist[k], 1}); //从第 1 层开始
while(q.size()){
PLI now = q.top(); q.pop();
int nod = now.second;
if(vis[nod]) continue;
vis[nod] = 1;
rep(i, 2, 3){
int To = (dist[nod] + a[i]) % a[1]; //下一结点
if(dist[To] > dist[nod] + a[i]){
dist[To] = dist[nod] + a[i];
q.push({dist[To], To});
}
}
}
}
int main(){
cin >> h;
rep(i, 1, 3) cin >> a[i];
sort(a + 1, a + 4);
if(a[1] == 1){
cout << h << endl;
return 0;
}
dij(1);
rep(i, 0, a[1] - 1)
if(dist[i] <= h) ans += (h - dist[i]) / a[1] + 1; //统计个数
cout << ans;
}
P2371墨墨的等式
- 就是把三个数变成了 n n n个数
- 最后也区间也不是 [ 1 , h ] [1,h] [1,h],而是 [ l , r ] [l,r] [l,r],就要像前缀和一样减一下前面的。
//同余最短路
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i <= (b); i++)
#define per(i, a, b) for(int i = (a); i >= (b); i--)
#define ll long long
#define PLI pair<ll, int>
#define pi acos(-1.0)
const int INF = 0x7fffffff;
const int N = 5e5 + 5;
using namespace std;
int n, a[N], vis[N];
ll l, r, dist[N];
void dij(){
memset(dist, 0x3f, sizeof(dist));
memset(vis, 0, sizeof(vis));
priority_queue<PLI, vector<PLI>, greater<PLI> > heap;
dist[0] = 0, heap.push({0, 0}); //加入初始节点
while(heap.size()){
PLI now = heap.top(); heap.pop();
int nod = now.second;
if(vis[nod]) continue;
vis[nod] = 1;
rep(i, 2, n){
int To = (dist[nod] + a[i]) % a[1];
if(dist[To] > dist[nod] + a[i]){
dist[To] = dist[nod] + a[i];
heap.push({dist[To], To});
}
}
}
}
int main(){
cin >> n >> l >> r;
rep(i, 1, n) scanf("%d", &a[i]);
sort(a + 1, a + 1 + n);
dij();
ll ans = 0;
rep(i, 0, a[1] - 1){ //[1, r] - [1, l - 1]的区间
if(dist[i] <= r){
ans += (r - dist[i]) / a[1] + 1;
if(dist[i] < l) ans -= (l - 1 - dist[i]) / a[1] + 1;
}
}
cout << ans << endl;
}