P3403 跳楼机 题解(同余最短路)
同余最短路在 OI Wiki 上的例题。
同余最短路的题目,在题面上看起来更类似于数学题,而不是图论题。
它可以解决以下问题:
- 给定
n 个整数,求这n 个整数能拼凑出多少的其他整数(n 个整数可以重复取); - 以及「给定
n 个整数,求这n 个整数不能拼凑出的最小(最大)的整数」; - 或者「至少要拼几次才能拼出模
K 余p 的数」的问题。
首先可以发现,
则本题的题意就是给定
设输入为
给定
首先,我们可以把
我们把
此时,我们只要求出每个同余类中,能够凑得的最小的数字,这样在该同余类中更大的数字也一定都能凑得。
比如说,
假设可以求出,每一个同余类
该式子可以这么理解:首先
而对于剩余的数,每增加
当然,如果
接下来,只要知道如何求出
构建同余最短路的图论模型前,首先可以先明确起点。
对于本题,当不进行任何操作的时候,一开始我们就在第一层,因此对于
(然而这里稍微有一点坑,可以看后面的解释)
之后再考虑如何图论建模。
现在我们把整段值域按照
因为无论如何
因此,我们利用
连边的方法是,对于每一个结点
对于每一个结点
边的意义是,假设当前有一个在同余类
并且,与原本的数字相比,转到另一个同余类后,数字的大小变大了
照这样子建完所有的边,就可以从刚才说的起点
到达每个结点的路径,就是凑出一个位于该同余类内的数字的一种方法,这段路径的边权和就是凑出的这个数字。
而凑出的最小的数字,刚好就是最短路径的距离,即
算出
设置起点时存在一个坑,如果弄错了就会 WA 在第一个测试点,得到 90 分。
设置起点时,我们会很自然地认为应该设置
显然是不存在的,因此设置起点时应该设置
代码如下:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
ll n; // 链式前向星表头习惯用 h,因此输入的 h 就用 n 来命名
int x, y, z;
// 点数:点数就是 x 的同余类的个数,x 最大为 N,则点需要 N 个
// 边数:每个点都需要往后利用 y 连一条边,再利用 z 连一条边,因此总共有 2*N 条边
int h[N], e[N << 1], nxt[N << 1], idx;
ll w[N << 1];
// dij 用到的数据结构
ll d[N];
bool vis[N];
priority_queue<pair<ll, int>, vector<pair<ll, int>>, greater<pair<ll, int>>> q;
ll ans;
// 添加一条从 f 指向 t,边权为 v 的边
void add(int f, int t, ll v)
{
e[idx] = t;
w[idx] = v;
nxt[idx] = h[f];
h[f] = idx;
idx++;
}
void dij(int s)
{
memset(d, 0x7f, sizeof(d)); // 初始化整个数组为无穷大
#if 0 // 按照这个方式设置起点是错的,上面说过了
d[s] = 1;
q.push({1, s});
#endif
d[s % x] = 1; // 正确的起点设置方法
q.push({1, s % x});
// 以下都是模板
pair<ll, int> t;
int sec, u;
ll fir, v;
while (!q.empty())
{
t = q.top();
q.pop();
fir = t.first;
sec = t.second;
if (vis[sec]) continue;
vis[sec] = true;
for (int i = h[sec]; ~i; i = nxt[i])
{
u = e[i];
v = w[i];
if (d[u] > d[sec] + v)
{
d[u] = d[sec] + v;
q.push({d[u], u});
}
}
}
}
int main(void)
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
memset(h, -1, sizeof(h)); // 初始化链式前向星表头
cin >> n;
cin >> x >> y >> z;
for (int i = 0; i < x; i++) // 对于每个同余类,往后连边
{
add(i, (i + y) % x, y); // 按照推导,利用 y、z 往后连边
add(i, (i + z) % x, z);
}
dij(1); // 从起点 1 跑单源最短路
for (int i = 0; i < x; i++)
{
if (d[i] <= n) // 注意条件
{
ans += (n - d[i]) / x + 1; // 统计答案
}
}
cout << ans;
return 0;
}