跳楼机
从来没见过同余最短路,确实有点意思的。此类题关键在于,其中某个元素的范围很小,我们需要以此为切入点解决。方便起见,我们从 开始出发,存在的最后一个楼层为 。
对于任何一个能到达的数,可以继续乘坐跳楼机上升 层若干次。因此可以发现,设有 ,其中 ,若可以到达 ,则一定可以到达 。
假设我们已经求出了仅使用 和 能到达的数集 。从 中任取一数 ,则任意 都可以达到,这样的 有 个。
但是,一个数可能会被重复统计。这是由于 中可能有多个数关于 同余。其中一个解决方案,即多个同余的数仅保留最小的,这是因为从最小的这个数 开始一直加 若干次后,一定可以到达其他与 模 同余的点。
现在,对于每个 ,我们想要知道集合中满足 的最小的点 (形式化地,我们要求 )。这里的一个相当聪明的步骤是转化为图论问题。
我们建立一个 个点的图,编号为 到 。对某一个点 ,我们建立边 和 。在这个图中,每条种移动方案都可以用一条路径描述:上升 层相当于沿着边权为 的移动一轮,上升 层相当于沿着边权为 的移动一轮,移动的距离即到达的高度。
从 出发跑最短路,设从 出发到点 的最短路为 。而 同时还是我们上面要求的 !
确实很聪明的转化。
#include <bits/extc++.h>
using namespace std;
namespace pbds = __gnu_pbds;
istream& fin = cin;
ostream& fout = cout;
using ui = unsigned int;
using uli = unsigned long long int;
using li = long long int;
vector<uli> dijkstra(size_t s, vector<vector<pair<size_t, uli>>> const& mp) {
vector<uli> dis(mp.size(), numeric_limits<uli>::max() / 2);
vector<bool> vis(mp.size());
priority_queue<pair<uli, size_t>, vector<pair<uli, size_t>>,
greater<pair<uli, size_t>>>
q;
dis[s] = 0;
q.emplace(0, s);
while (!q.empty()) {
size_t p = q.top().second;
q.pop();
if (vis[p]) continue;
vis[p] = true;
for (auto i : mp[p])
if (dis[p] + i.second < dis[i.first])
q.emplace(dis[i.first] = dis[p] + i.second, i.first);
}
return dis;
}
int main(void) {
ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
uli h;
ui x, y, z;
fin >> h >> x >> y >> z;
--h;
vector<vector<pair<size_t, uli>>> mp(x);
for (size_t i = 0; i < x; ++i)
mp[i].emplace_back((i + y) % x, y), mp[i].emplace_back((i + z) % x, z);
auto dis = dijkstra(0, mp);
uli ans = 0;
for (size_t i = 0; i < x; ++i)
if (dis[i] <= h) ans += (h - dis[i]) / x + 1;
fout << ans;
return 0;
}