• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
jacklee404
Never Stop!
博客园    首页    新随笔    联系   管理    订阅  订阅
Acwing-提高课 图论

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\)是未知的,但是终点是已知的,所以就可以求一个反转的最短路。

\[x(1 - z) = money \\ \frac{money}{1-z} = 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选点和松弛的时候加上限制

\[abs(level[j] - level[1]) \le m \\ abs(level[t] - level[v]) \le m \space and \space abs(level[v] - level[1]) \le m \]

​ 但是这样做并不对,我们只考虑两个直接相连的点的关系,但是题目要求是,最短路上所有点都必须满足的关系, 我们考虑一种情况,\(m = 1, path_{level}<1, 2, 3, 2>\)同样满足上面的关系, 但是我们已经购买了1, 所以不能和有3的进行交易。

​ 由于区间很小, 我们可以考虑暴力,我们只对区间内的点松弛, 这个区间应该满足

  1. 在 \([L_1 - m, L_1 + m]\)内
  2. 区间内的点应该满足左右区间最大数不超过\(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;	
	}
}
posted on 2023-04-03 10:34  Jack404  阅读(13)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3