当前位置: 首页 > news >正文

【同余最短路】学习笔记

例题 \(1\):P3403 跳楼机:给定正整数 \(h,x,y,z\),求有多少 \(d\in[1,h]\) 满足 \(ax+by+cz=d-1\),其中 \(a,b,c\) 为非负整数。

这道题第一眼给我的印象是一道数论题,但仔细想了想发现做不了。注意到 \(x,y,z\) 的范围较小,尝试从这个条件入手。

为方便起见,不妨将 \(h\)\(1\),那么初始楼层变为第 \(0\) 层,\(d\) 的范围改为 \([0,h-1]\)

注意到如果设 \(\mu=by+cz\),那么 \(\lambda x+\mu\)\(\lambda\in\mathbf{N}\))一定能够通过跳楼机到达。

因此我们考虑这样一个函数 \(f(i)\),其表示跳若干次 \(y\)\(z\) 后到达满足层数模 \(x\) 等于 \(i\) 的最小楼层,更数学一点表述就是 \(f(i)\) 等于满足 \((by+cz)\bmod x=i\) 的最小的 \(by+cz\)。根据定义,我们可以得出 \(f(i)\) 的两条性质:\(f(i)+y\ge f((i+y)\bmod x),f(i)+z\ge f((i+z)\bmod x)\)

这使我们联想到最短路中的 \(dist\) 函数。对于一张我们跑完一遍 Dijkstra 算法后的图,其每条边 \((u,v,w)\) 都应满足 \(dist(u)+w\ge dist(v)\)。这个式子的形式与上面的 \(f\) 函数是一致的。

因此我们可以考虑建图,按照对应关系,我们建一张包含 \(x\) 个点的有向带权图,点的编号为 \(0\sim x-1\),对于每个 \(i\in[0,x-1]\),我们建立有向边 \((i,(i+y)\bmod x,y)\)\((i,(i+z)\bmod x,z)\)

建完图后,跑一遍 Dijkstra 求得 \(dist\),这里的 \(dist\) 实际上就是上文的 \(f\) 函数。

那么对于每个点 \(i\),其对答案的贡献即为 \(\left\lfloor \dfrac{h-dist(i)}{x} \right\rfloor+1\)。所以答案即为 \(\displaystyle \sum^{x-1}_{i=0}\left\lfloor \dfrac{h-dist(i)}{x} \right\rfloor+1\)

代码实现如下:

#include<bits/stdc++.h>
#define int long long
#define PII pair<int, int>using namespace std;const int N = 1e5 + 10, INF = LLONG_MAX;int h, x, y, z, ans = 0;
vector<PII> e[N];int dist[N];
bool st[N];void dijkstra(int s)
{priority_queue<PII> q;for(int i = 0; i < N; i ++){dist[i] = INF;}dist[s] = 0;q.push({0, s});while(q.size()){int u = q.top().second;q.pop();if(st[u]){continue;}st[u] = true;for(auto i : e[u]){int v = i.second, w = i.first;if(dist[v] > dist[u] + w){dist[v] = dist[u] + w;q.push({-dist[v], v});}}}
}signed main()
{cin >> h >> x >> y >> z;h --;for(int i = 0; i < x; i ++){e[i].push_back({y, (i + y) % x});e[i].push_back({z, (i + z) % x});}dijkstra(0);for(int i = 0; i < x; i ++){if(h >= dist[i]){ans += (h - dist[i]) / x + 1;}}cout << ans;return 0;
}

例题 \(2\):P2371 [国家集训队] 墨墨的等式。

本题其实只是把上一题的 \(3\) 个数改为了 \(n\) 个,所以建图方式是一致的。

在统计答案时,考虑前缀和的思想,单点贡献为从 \(r\) 中扣掉 \(l-1\) 的部分。

注意 \(a_i=0\) 的数据要去掉。

#include<bits/stdc++.h>
#define int long long
#define PII pair<int, int>using namespace std;const int N = 5e5 + 10, M = 20, INF = LLONG_MAX;int n, m, mi = INF, id, ans = 0;
int l, r;
int a[M];
vector<PII> e[N];int dist[N];
bool st[N];void dijkstra(int s)
{priority_queue<PII> q;for(int i = 0; i < N; i ++){dist[i] = INF;}dist[s] = 0;q.push({0, s});while(q.size()){int u = q.top().second;q.pop();if(st[u]){continue;}st[u] = true;for(auto i : e[u]){int v = i.second, w = i.first;if(dist[v] > dist[u] + w){dist[v] = dist[u] + w;q.push({-dist[v], v});}}}
}signed main()
{cin >> n >> l >> r;l --;for(int i = 1; i <= n; i ++){int x;scanf("%lld", &x);if(x){a[++ m] = x;if(mi > x){mi = x;id = m;}}}for(int i = 0; i < mi; i ++){for(int j = 1; j <= m; j ++){if(j == id){continue;}e[i].push_back({a[j], (i + a[j]) % mi});}}dijkstra(0);for(int i = 0; i < mi; i ++){if(r >= dist[i]){ans += (r - dist[i]) / mi + 1;}if(l >= dist[i]){ans -= (l - dist[i]) / mi + 1;}}cout << ans;return 0;
}

例题 \(3\):Small Multiple:给定正整数 \(K\),求 \(K\) 的所有倍数的最小数位和。

每一个正整数都可以由 \(1\) 出发,仅经过若干次两种变换得到:

  1. 把数 \(+1\)
  2. 把数 \(\times 10\)

因为两种操作保证了可以任意决定数字的位数与每位上的数字是什么。

那么我们可以用每个数字代表一个点,点与点之间的连边表示两种操作。因为只有操作一会对数位和产生 \(1\) 的贡献,因此可以根据两种操作的类型给边权赋为 \(1/0\)。但这样做会有点小问题,如果对于每个数都建一个点空间不够,解决方案是由于同余,所以我们只需要开 \(0\sim K-1\)\(K\) 个点即可。那么初始点为 \(1\),我们要到达的 \(K\) 的倍数对应的点为 \(0\)

由于边权仅有 \(0\)\(1\),因此我们只需要跑一个 01BFS 即可。

#include<bits/stdc++.h>
#define PII pair<int, int>using namespace std;const int N = 1e5 + 10, INF = 1e9;int K;
vector<PII> e[N];int dist[N];
bool st[N];void bfs(int s)
{deque<int> q;for(int i = 0; i < N; i ++){dist[i] = INF;}dist[s] = 1;q.push_back(s);while(q.size()){int u = q.front();q.pop_front();if(st[u]){continue;}st[u] = true;for(auto i : e[u]){int v = i.second, w = i.first;if(dist[v] > dist[u] + w){dist[v] = dist[u] + w;if(st[v] == false){if(w){q.push_back(v);}else{q.push_front(v);}}}}}
}int main()
{cin >> K;for(int i = 0; i < K; i ++){e[i].push_back({1, (i + 1) % K});e[i].push_back({0, (i * 10) % K});}bfs(1);cout << dist[0];return 0;
}
http://www.hskmm.com/?act=detail&tid=38331

相关文章:

  • 数字人:数字人公司深度解析与未来展望
  • CSP/NOIP 复习:单调栈
  • 算法分析--生成排列
  • 三大安全认证授权协议深度对比:OAuth、OpenID Connect与SAML
  • 数字人公司:数字人新趋势技术驱动与市场前景解析
  • AI股票预测分析报告 - 2025年10月24日
  • 数据绑定相关概念理解
  • 数字人企业:数字人公司排行榜Top 3解析
  • (简记)(自用)线段树区间拆分时间复杂度证明
  • 数字人企业:数字人公司排行榜深度解析
  • 数字人:怎么选择数字人实力公司
  • 拉格朗日插值优化DP
  • 冬日绘板 2026 珂朵莉计划 如何获取 Token
  • 数字人企业:数字人公司技术驱动的三大标杆
  • Linux下的拼音输入法 (2)
  • 数字人平台:重点推荐优质数字人公司
  • SpringBoot整合缓存2-Redis
  • 数字人企业:推荐数字人TOP3公司
  • NOI25D2T2
  • 时钟同步
  • 深入解析:【Java系列课程Java学前须知】第3课 JDK,JVM,JRE的区别和优缺
  • 10.24 CSP-S 模拟37 改题记录
  • 数字人企业:数字人公司重点推荐与选择指南
  • C++实验二
  • 据说每邀请一位朋友加入Comet,您可以获得10刀乐奖励:D
  • 2025.10.24NOIP
  • 小程序 访问第三方网页
  • 王炸!OpenAI 发布 Atlas 浏览器!!
  • 国产开源数据库调研项目的LaTeX专业排版实践
  • Asterix cat-062 ,航班号字段的编码解码