同余最短路

计组课划水研究了一下(

参考:OI Wiki

介绍

常见题型

  • 给定 n 个整数,求这 n 个整数能拼凑出多少的其他整数( n 个整数可以重复取)
  • 给定 n 个整数,求这 n 个整数不能拼凑出的最小(最大)的整数
  • 至少要拼几次才能拼出模 k 余 p 的数

简介

同余最短路利用同余来构造一些状态,可以达到优化空间复杂度的目的

类比差分约束方法,利用同余构造的这些状态可以看作单源最短路中的点。同余最短路的状态转移通常是这样的 f(i+y) = f(i) + y,类似单源最短路中 f(v) = f(u) + edge(u,v)

例题

P3403 跳楼机

传送门:https://www.luogu.com.cn/problem/P3403

题目大意:给定x, y, z, h,对于 k ∈ [1,h],有多少个 k 能满足 ax + by + cz = k

令 dis(i) 表示仅通过 操作 2操作 3 能到达的 mod x = i 的最小楼层

可以得到如下两个状态:

  • dis(i + y) = dis(i) + y
  • dis(i + z) = dis(i) + z

那么实际上相当于执行了最短路中的建边操作:

  • add(i, (i + y) % x, y)
  • add(i, (i + z) % x, z)

跑一次最短路就可以求出相应的 dis(i),答案即为 ans += (h − dis[i]) / x + 1(+1 是因为当前所在楼层也算)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 100010;
const int INF = 0x3f3f3f3f;
ll h, x, y, z;
ll head[maxn << 1], tot;
ll dis[maxn], vis[maxn];
queue<int> q;
struct edge {
  ll to, next, w;
} e[maxn << 1];
void add(ll u, ll v, ll w) {
  e[++tot] = edge{v, head[u], w};
  head[u] = tot;
}
void spfa() {
  dis[1] = 1;
  vis[1] = 1;
  q.push(1);
  while (!q.empty()) {
    int u = q.front();
    q.pop();
    vis[u] = 0;
    for (int i = head[u]; i; i = e[i].next) {
      int v = e[i].to, w = e[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(dis, INF, sizeof(dis));
  scanf("%lld", &h);
  scanf("%lld %lld %lld", &x, &y, &z);
  if (x == 1 || y == 1 || z == 1) {
    printf("%d\n", h);
    return 0;
  }
  for (int i = 0; i < x; i++) {
    add(i, (i + z) % x, z);
    add(i, (i + y) % x, y);
  }
  spfa();
  ll ans = 0;
  for (int i = 0; i < x; i++) 
    if (h >= dis[i]) ans += (h - dis[i]) / x + 1;
  printf("%lld\n", ans);
  return 0;
}

P2662 牛场围栏

传送门:https://www.luogu.com.cn/problem/P2662

#include <bits/stdc++.h>
using namespace std;
const int maxn = 100010;
const int INF = 0x3f3f3f3f;
int n, m, x, ans, a[maxn];
int head[maxn << 1], tot;
int dis[maxn], vis[maxn];
queue<int> q;
struct edge {
  int to, next, w;
} e[maxn << 1];
void add(int u, int v, int w) {
  e[++tot] = edge{v, head[u], w};
  head[u] = tot;
}
void spfa() {
  memset(dis, 0x3f, sizeof(dis));
  dis[0] = 0;
  q.push(0);
  while (!q.empty()) {
    int u = q.front();
    q.pop();
    vis[u] = 0;
    for (int i = head[u]; i; i = e[i].next) {
      int v = e[i].to, w = e[i].w;
      if (dis[v] > dis[u] + w) {
        dis[v] = dis[u] + w;
        if (!vis[v]) {
          q.push(v);
          vis[v] = 1;
        }
      }
    }
  }
}
int main() {
  scanf("%d%d", &n, &m);
  for (int i = 1; i <= n; i++)
    scanf("%d", &a[i]);
  sort(a + 1, a + 1 + n);
  x = max(1, a[1] - m);
  if (x == 1) {
    printf("-1");
    return 0;
  }
  for (int i = 1; i <= n; i++)
    for (int j = max(a[i - 1] + 1, a[i] - m); j <= a[i]; j++) 
      if (j != x) for (int k = 0; k < x; k++) add(k, (k + j) % x, j);
  spfa();
  dis[x] = 0;
  for (int i = 1; i < x; i++) {
    if (dis[i] > 100000000) {
      printf("-1");
      return 0;
    }
    ans = max(ans, dis[i] - x);
  }
  printf("%d", ans);
  return 0;
}
posted @ 2022-03-10 20:42  Moominn  阅读(207)  评论(0编辑  收藏  举报