同余最短路总结
DarkMoon_Dragon · · 题解
20190928 重新修了一下此篇博客,修改了码风
同余最短路,并不是用同余来跑最短路,而是通过同余来构造某些状态,从而达到优化时间空间复杂度的目的。往往这些状态就是最短路中的点,可以类比差分约束跑最短路(f[i]+w<=f[j] 构造最短路不等式(如 小k的农场))
luogu P3403 跳楼机
题意:求
类比然而当年手推exgcd),区别就在货币系统限制了货币的上限
先考虑简单情况,假设只有两个数
设dis[i] 表示到i号状态点时,所需花费的最小a*x ,也就是要达到状态ax\%b==i 使用a 跳楼机的最小次数x 。
这里有两个地方需要理解:
- i号状态点: 表示的是
ax\%y==i 的状态,将此状态抽象成一个点。因为y 有系数b ,每对当前状态i 取一个y ,都会对应一个新的楼层。这样用最大高度减去当前dis[i] 去除系数b 向下取整得到就是可以跳到的楼层数量。 - 花费最小的x: 限制了
x 最小,即a*x 最小,这样就可以用留下更多的y 来得到更大的答案。
建边方式
adde(u = i, v = ( i + a ) % b, w = a);
统计答案
for ( i = 0; i < y; ++i) if (h >= dis[i]) ans += (h - dis[i]) / b + 1;
统计答案时直接算
然后考虑如题情况,三元不定方程
建边方式为 adde(i, (i+a)%c, a) 和 adde(i, (i + b) % c, b)
统计答案仍为对于每一个状态点
即if ( h >= dis[i] ) ans += (h - dis[i]) / c + 1;
拓展:如果要单独讨论
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<queue>
#define rr register int
using namespace std;
typedef long long ll;
inline ll read(){
char i = getchar();
ll dp = 1, res = 0;
while (i < '0' || i > '9') {
if (i == '-') dp = -1;
i = getchar();
}
while (i >= '0' && i <= '9') {
res = res * 10 + i - '0';
i = getchar();
}
return res * dp;
}
const int N = 1e5 + 50;
struct zk{
int v, next;
ll w;
}ed[N << 2];
int head[N], cnt=0, vis[N];
ll dis[N];
inline void adde(int u, int v, ll w){
ed[++cnt].v = v;
ed[cnt].w = w;
ed[cnt].next = head[u];
head[u] = cnt;
}
ll h, x, y, z;
queue<int> q;
void SPFA(){ //SPFA 板子
dis[1] = 1;
vis[1] = 1;
q.push(1);
while (!q.empty()) {
int u = q.front();
vis[u] = 0;
q.pop();
for (rr i = head[u]; ~ i; i = ed[i].next) {
int v = ed[i].v, w = ed[i].w;
if (dis[v] > dis[u] + w){
dis[v] = dis[u] + w;
if (!vis[v]) {
q.push(v);
vis[v] = 1;
}
}
}
}
}
int main(){
memset(head, -1, sizeof(head));
memset(dis, 0x3f3f3f3f, sizeof(dis));
h = read(), x = read(), y = read(), z=read();
for (rr i = 0; i < z;++i) {
adde(i, (i + x)% z, x);
adde(i, (i + y)% z, y);//表示从i到下一个点(i+y)%z
}
SPFA();
ll ans = 0;
for(rr i = 0; i < z; ++i) {
if(h >= dis[i])
ans += (h - dis[i]) / z + 1;
}
printf("%lld\n",ans);
}