同余最短路 (学习笔记)(25.11.19)
同余最短路(学习笔记)
概述
- 用于去求取在模\(m\)意义下,一系列运算后,余数为\(x\)的情况下,该运算的最小值
e.g.
\[(by+cz)modm = x\\
求取min(by+cz)
\]
- 可以设
\[f(i) 为 当(by+cz)modm=i\\
f(i) = min(by+cz)
\]
- 该函数存在一个性质:
\[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
\]
- 使用最短路在该意义下,跑一遍公式即可
差分约束系统
- 可以将此系统抽象成为一个图论,具体为如下:
一个数组包含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的解 - 首先我们可以把原本的约束条件做出变形\(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\)的有向边,使用最短路求解
- 实现: 设\(dis[0] = 0\)并且向每个点连一条权值为0的边(本质是表示每个\(x_i\)减0的约束条件为0),后面去跑最短路,如果有负环则无解,否则最后\(x_i = dis[i]\)为该系统的解
同余最短路的实现
- 注意对于最大值的处理
- 注意建边是对于\(<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;
}

浙公网安备 33010602011771号