同余最短路 (学习笔记)(25.11.19)

同余最短路(学习笔记)

概述

  1. 用于去求取在模\(m\)意义下,一系列运算后,余数为\(x\)的情况下,该运算的最小值
    e.g.

\[(by+cz)modm = x\\ 求取min(by+cz) \]

  1. 可以设

\[f(i) 为 当(by+cz)modm=i\\ f(i) = min(by+cz) \]

  1. 该函数存在一个性质:

\[f(i) + y \geq f((i+y)modm)\\ f(i) + z \geq f((i+z)modm) \]

可以理解为,在 \(f(i)\) 的基础上,加\(y\)到达的数,只能大于等于\(f(模m意义下的i+y)\)

这其实是一种差分约束系统

\[f((i+z)modm) - f(i) \leq z \]

表示在\(i+z\)的最优状态下减去\(i\)的最优状态下,得到的数一定是小于等于一个\(z\)

同时就可以将这个式子去对应到最短路公式

\[dis[v] \le dis[u] + w_u \]

  1. 使用最短路在该意义下,跑一遍公式即可

差分约束系统

  1. 可以将此系统抽象成为一个图论,具体为如下:
    一个数组包含n个变量为\(x_1,x_2,…,x_n\)以及有m个约束条件,条件是由两个变量做差构成,比如:\(x_i-x_j\le c_k\),其中\(1\le i,j \le n, i \ne j,1 \le k \le m\)并且\(c_k\)是常数,我们需要做的是怎么去求解出每个x的解
  2. 首先我们可以把原本的约束条件做出变形\(x_i - x_j\le c_k \Rightarrow x_i \le x_j+c_k\)可以发现,这个式子与单源最短路的公式很相似\(dis[v] \le dis[u] + w_u\),所以我们就可以把每个\(x_i\)看做一个点,之后以\(i\)\(j\)可以连出一条边权为\(c_k\)的有向边,使用最短路求解
  3. 实现:\(dis[0] = 0\)并且向每个点连一条权值为0的边(本质是表示每个\(x_i\)减0的约束条件为0),后面去跑最短路,如果有负环则无解,否则最后\(x_i = dis[i]\)为该系统的解

同余最短路的实现

  1. 注意对于最大值的处理
  2. 注意建边是对于\(<x\)的每个能到达的点之间去建边,然后求一个最短路
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6*2+10;
long long H, x, y, z;
const unsigned long long inf = (1<<61)-1;
#define int long long
struct edge {
    int w, to, nxt;
}s[N*2];
int h[N], cnt=0;
void add(int u, int to, int w) {
    s[++cnt] = {w,to,h[u]};
    h[u] = cnt;
}

struct node{
    long long dis;
    int pos;
    bool operator < (const node &x) const {
        return x.dis<dis;
    }
};
priority_queue<node> q;
unsigned long long dis[N]; int vis[N];
void dijkstra(int x) {
    for (int i=0; i<N; i++) {
        dis[i] = inf;
    }
    // memset(dis,0x7f,sizeof(dis));
    dis[x] = 0;
    q.push({0,x});
    while (!q.empty()) {
        node t = q.top();q.pop();
        long long u=t.pos;
        if (vis[u]) continue;
        vis[u]=1;
        for (int i=h[u]; i; i=s[i].nxt) {
            int v=s[i].to;
            if (dis[v]>dis[u]+s[i].w) {
                dis[v] = dis[u]+s[i].w;
                q.push({dis[v],v});
            }
        }
    }
}

signed main() {
    freopen("jump.in","r",stdin);
    freopen("jump.out","w",stdout);
    ios::sync_with_stdio(0), cin.tie(0);
    cin>>H>>x>>y>>z;

    if (x==1||y==1||z==1) {
        cout<<H;return 0;
    }H--;
    for (int i=0; i<x; i++) {
        add(i,(i+y)%x,y);
        add(i,(i+z)%x,z);
    }
    dijkstra(1);
    long long ans=0;
    for (int i=0; i<x; i++) {
        if (H>=dis[i]) ans+=(H-dis[i])/x+1;
    }
    cout<<ans;
    return 0;
}

差分约束的实现

先去建边,这里主要的是需要一个超级源点,就是需要让所有的点都进入队列,然后我们就可以让其继续去跑一个最短路,因为每个约束条件其都必须实现,所以不用担心初始化的问题

如果要约束一个相等条件,就可以双向建边,然后让\(\le和\ge\)同时成立,取交集就是\(=\)

反之,要求最大值就反向建边,然后跑最短路就行

#include <bits/stdc++.h>
using namespace std;
const int N = 1e3*5+10;
int n, m;

struct edge {
    int to, nxt, w;
}a[N*2];
int h[N], cnt1=0;
void add(int u, int to, int w) {
    a[++cnt1] = {to, h[u],w};
    h[u] = cnt1;
}

queue <int> q;
int dis[N], vis[N], cnt[N];
bool spfa(int x) {
    memset(dis,0x3f,sizeof(dis));
    dis[x]=0;
    vis[x]=1;
    cnt[x]=1;
    q.push(x);
    while (!q.empty()) {
        int u = q.front();
        q.pop();
        vis[u] = 0;
        for (int i=h[u]; i; i = a[i].nxt) {
            int v = a[i].to;
            if (dis[v]>dis[u]+a[i].w) {
                dis[v] = dis[u]+a[i].w;
                cnt[v] = cnt[u]+1;
                if (!vis[v]) q.push(v), vis[v]=1;
            }
            if (cnt[v]>=n+n+1) {
                return false;
            }
        }
    }
    return true;
}
int main() {
    cin>>n>>m;

    for (int i=1; i<=m; i++) {
        int c, c_, y;
        cin>>c>>c_>>y;
        add(c_,c,y);
    }
    for (int i=1; i<=n; i++) {
        add(0,i,0);
    }

    if (!spfa(0)) {
        cout<<"NO";
    }else {
        for (int i=1; i<=n; i++) {
            cout<<dis[i]<<" ";
        }
    }
    return 0;
}
posted @ 2025-11-19 19:58  Yuriha  阅读(6)  评论(0)    收藏  举报