Acwing-提高课 图论
单源最短路的建图方式
1129. 热浪
注意无向图的边数为题目数据的2倍,另外链式前向星只有h[] 数组存储的大小是点数,其他是边数, 存储的时候可以不用结构体,用\(w\)数组存储idx对应的边权
#include <iostream>
#include <cstring>
using i64 = long long;
const int M = 6200 * 2 + 10, N = 2700;
struct node {
int v, w;
};
int n, m, s1, e1;
int idx, ne[M], h[N], dist[N];
bool st[N];
node e[M];
void add(int a, int b, int c) {
ne[idx] = h[a], h[a] = idx, e[idx ++] = {b, c};
}
void dijkstra(int s1) {
memset(dist, 0x3f, sizeof dist);
dist[s1] = 0;
for (int i = 0; i < n - 1; i ++) {
int t = -1;
for (int j = 1; j <= n; j ++)
if (!st[j] && (t == -1 || dist[t] > dist[j]))
t = j;
st[t] = true;
for (int j = h[t]; ~j; j = ne[j]) {
int v = e[j].v, w = e[j].w;
dist[v] = std::min(dist[v], dist[t] + w);
}
}
}
int main() {
memset(h, -1, sizeof h);
std::cin >> n >> m >> s1 >> e1;
for (int i = 1; i <= m; i ++) {
int a, b, c;
std::cin >> a >> b >> c;
add(a, b, c);
add(b, a, c);
}
dijkstra(s1);
std::cout << dist[e1];
}
1128.信使
图论的题目其实偏难理解,难建模,比赛当然不会考简单的最短路或者其他的问题的,更多的是如何抽象出题目的问题转化为最短路问题,或抽象建图的过程。
该题目求从源点广播,到所有点都收到数据的时间,显然从源点到某个点的最短路就是该最短路径树的子图, 所以我们可以答案就是最长的最短路.
#include <iostream>
#include <cstring>
#include <queue>
using i64 = long long;
int n, m;
int e[3000], h[600], ne[3000], w[3000], dist[3000], idx;
bool st[3000];
void add(int a, int b, int c) {
w[idx] = c, ne[idx] = h[a], h[a] = idx, e[idx ++] = b;
}
int dijkstra(int s) {
memset(dist, 0x3f, sizeof dist);
std::priority_queue<std::pair<int, int>> h1;
h1.push({0, s});
dist[s] = 0;
while (h1.size()) {
auto t = h1.top(); h1.pop();
int v = t.second;
if (st[v]) continue;
st[v] = true;
for (int i = h[v]; ~i; i = ne[i]) {
int u1 = e[i], w1 = w[i];
if (dist[u1] > dist[v] + w1) {
dist[u1] = dist[v] + w1;
h1.push({-dist[u1], u1});
}
}
}
int ans = -1;
for (int i = 1; i <= n; i ++) {
if (dist[i] == 0x3f3f3f3f) {
return -1;
}
ans = std::max(ans, dist[i]);
}
return ans;
}
int main() {
memset(h, -1, sizeof h);
std::cin >> n >> m;
for (int i = 1; i <= m; i ++) {
int a, b, d;
std::cin >> a >> b >> d;
add(a, b, d);
add(b, a, d);
}
std::cout << dijkstra(1);
}
1127.香甜的奶油
一开始考虑从源点开始,没想到题目读错了, 这里是扣除手续费, 后来发现正着求最短路不能求,\(x\)为之前的钱币,则每次经过一个节点都要转换一下钱,但是我们的\(x\)是未知的,但是终点是已知的,所以就可以求一个反转的最短路。
其实也可以从源点开始,初始化源点为\(1\), 最后ans即为\(100 / dist[e1]\)
#include <iostream>
#include <cstring>
using i64 = long long;
const double EPS = 1e-8;
int n, m;
int e[200010], ne[200010], h[2010], w[200010], idx;
double dist[2010];
bool st[2010];
void add(int a, int b, int k) {
ne[idx] = h[a], h[a] = idx, w[idx] = k, e[idx ++] = b;
}
void dijkstra(int s) {
for (int i = 1; i <= n; i ++) dist[i] = 0x3f3f3f3f;
dist[s] = 100;
for (int i = 0; i < n - 1; i ++) {
int t = -1;
for (int j = 1; j <= n; j ++)
if (!st[j] && (t == -1 || dist[t] > dist[j]))
t = j;
st[t] = true;
for (int j = h[t]; ~j; j = ne[j]) {
int &v = e[j], w1 = w[j];
if (dist[v] - dist[t] / (1 - (w1 * 1.0 / 100)) > EPS) {
dist[v] = dist[t] / (1 - (w1 * 1.0 / 100));
}
}
}
}
int main() {
memset(h, -1, sizeof h);
std::cin >> n >> m;
for (int i = 1; i <= m; i ++) {
int x, y, z;
std::cin >> x >> y >> z;
add(x, y, z);
add(y, x, z);
}
int s1, e1;
std::cin >> s1 >> e1;
dijkstra(e1);
printf("%.8lf", dist[s1]);
}
1126.最小花费
#include <iostream>
#include <cstring>
using i64 = long long;
const double EPS = 1e-8;
int n, m;
int e[200010], ne[200010], h[2010], w[200010], idx;
double dist[2010];
bool st[2010];
void add(int a, int b, int k) {
ne[idx] = h[a], h[a] = idx, w[idx] = k, e[idx ++] = b;
}
void dijkstra(int s) {
for (int i = 1; i <= n; i ++) dist[i] = 0x3f3f3f3f;
dist[s] = 100;
for (int i = 0; i < n - 1; i ++) {
int t = -1;
for (int j = 1; j <= n; j ++)
if (!st[j] && (t == -1 || dist[t] > dist[j]))
t = j;
st[t] = true;
// std::cout << t << "\n";
for (int j = h[t]; ~j; j = ne[j]) {
int &v = e[j], w1 = w[j];
if (dist[v] - dist[t] / (1 - (w1 * 1.0 / 100)) > EPS) {
dist[v] = dist[t] / (1 - (w1 * 1.0 / 100));
// std::cout << dist[t] << "\n";
}
}
}
}
int main() {
memset(h, -1, sizeof h);
std::cin >> n >> m;
for (int i = 1; i <= m; i ++) {
int x, y, z;
std::cin >> x >> y >> z;
add(x, y, z);
add(y, x, z);
}
int s1, e1;
std::cin >> s1 >> e1;
dijkstra(e1);
printf("%.8lf", dist[s1]);
}
903.昂贵的聘礼
比较好的一道思维题, 对于最短路问题,一般难点在于建图,这道题建图并不难,建立一个超级源点0, 连接图内任意一个点为直接购买该物品的费用,一开始做的时候我的想法是在Dijkstra选点和松弛的时候加上限制
但是这样做并不对,我们只考虑两个直接相连的点的关系,但是题目要求是,最短路上所有点都必须满足的关系, 我们考虑一种情况,\(m = 1, path_{level}<1, 2, 3, 2>\)同样满足上面的关系, 但是我们已经购买了1, 所以不能和有3的进行交易。
由于区间很小, 我们可以考虑暴力,我们只对区间内的点松弛, 这个区间应该满足
- 在 \([L_1 - m, L_1 + m]\)内
- 区间内的点应该满足左右区间最大数不超过\(m\) 即 \(r - l \le m\)
所以我们可以从\(l = L_1 - m\), 枚举,区间长度为\(m + 1\)
复杂度\(\Theta(NM \times m)\)
#include <iostream>
#include <queue>
#include <cstring>
using i64 = long long;
const int N = 110, M = 1e4 + 10;
int h[N], e[M], ne[M], w[M], idx;
int dist[N], level[N], m, n;
bool st[N];
void add(int a, int b, int c) {
ne[idx] = h[a], h[a] = idx, e[idx] = b, w[idx ++] = c;
}
int spfa(int l, int r) {
memset(st, false, sizeof st);
memset(dist, 0x3f, sizeof dist);
std::queue<int> q1;
dist[0] = 0;
q1.push(0);
while (q1.size()) {
int t = q1.front(); q1.pop();
st[t] = false;
for (int i = h[t]; ~i; i = ne[i]) {
int v = e[i];
if (dist[v] > dist[t] + w[i] && (level[v] >= l && level[v] <= r)) {
dist[v] = dist[t] + w[i];
q1.push(v);
if (!st[v]) q1.push(v), st[v] = true;
}
}
}
return dist[1];
}
int main() {
memset(h, -1, sizeof h);
std::cin >> m >> n;
for (int i = 1; i <= n; i ++) {
int p, l, x;
std::cin >> p >> l >> x;
add(0, i, p);
level[i] = l;
while (x --) {
int a, b;
std::cin >> a >> b;
add(a, i, b);
}
}
int ans = 0x3f3f3f3f;
for (int i = level[1] - m; i <= level[1]; i ++) {
int l = i, r = i + m;
ans = std::min(ans, spfa(l, r));
}
std::cout << ans;
}
920.最优乘车
这里用到了一个小技巧 调用sstream
这个库,可以读取未知长度的分割字符串
先#include <sstream>
, 然后使用getline(std::cin, line)
, 在std::stringstream ssin(line)
, 然后使用
while (ssin >> p) a[++ cnt] = p
来读取
思路主要是建图上, 由于我们每次到站换乘, 我们可以对每一个大巴建立\(w_{ij}, i \le j 在大巴路线中\)边权为\(1\)的有向图,这样我们用边权为\(1\)表示从某个站到某个站下站, 那么\(换乘次数\)就是\(下站次数-1\)
#include <iostream>
#include <sstream>
#include <cstring>
#include <queue>
using i64 = long long;
const int N = 510;
bool g[N][N];
int stop[N], dist[N], m, n;
void bfs() {
memset(dist, -1, sizeof dist);
std::queue<int> q1;
q1.push(1);
dist[1] = 0;
while (q1.size()) {
int t = q1.front(); q1.pop();
if (t == n) {
return;
}
for (int i = 1; i <= n; i ++) {
if (g[t][i] && dist[i] == -1) {
q1.push(i);
dist[i] = dist[t] + 1;
}
}
}
}
int main() {
std::cin >> m >> n; getchar();
std::string line;
while (m --) {
getline(std::cin, line);
std::stringstream ssin(line);
int cnt = 0, p;
while (ssin >> p) stop[++ cnt] = p;
for (int i = 1; i <= cnt; i ++)
for (int j = i + 1; j <= cnt; j ++)
g[stop[i]][stop[j]] = true;
// std::cout << stop[i] << " " << stop[j] << "\n";
}
// std::cout << g[1][5] << "\n";
bfs();
if (dist[n] == -1) {
puts("NO");
} else {
std::cout << dist[n] - 1;
}
}