第六章 图论

AcWing 1475. 紧急情况

问题描述

  • 问题链接:AcWing 1475. 紧急情况、原题链接

分析

  • 本题相当于给定我们一个无向图,让我们求两点之间最短路径的数量。并从这些最短路径中选取一条,使得点权之和最大。

  • 使用数组dist记录从起点到每个点的最短路径,使用数组cnt记录从起点到每个点的最短路径条数,使用数组sum记录从起点到每个点的所有最短路径中点权之和最大值。

代码

  • C++
#include <iostream>
#include <cstring>using namespace std;const int N = 510;int n, m, S, T;
int w[N];  // 每个城市救援队数量
int d[N][N];  // 邻接矩阵
int dist[N], cnt[N], sum[N];  // dist: 最短路径长度; cnt:最短路径数量; sum: 最短路径上救援队的数量
bool st[N];  // 判重数组void dijkstra() {memset(dist, 0x3f, sizeof dist);dist[S] = 0, cnt[S] = 1, sum[S] = w[S];for (int i = 0; i < n; i++) {int t = -1;for (int j = 0; j < n; j++)if (!st[j] && (t == -1 || dist[t] > dist[j]))t = j;st[t] = true;for (int j = 0; j < n; j++) if (dist[j] > dist[t] + d[t][j]) {  // 需要更新到j的最短路径dist[j] = dist[t] + d[t][j];cnt[j] = cnt[t];sum[j] = sum[t] + w[j];} else if (dist[j] == dist[t] + d[t][j]) {  // 存在多条到j的最短路径cnt[j] += cnt[t];sum[j] = max(sum[j], sum[t] + w[j]);}}
}int main() {cin >> n >> m >> S >> T;for (int i = 0; i < n; i++) cin >> w[i];memset(d, 0x3f, sizeof d);for (int i = 0; i < m; i++) {int a, b, c;cin >> a >> b >> c;d[a][b] = d[b][a] = min(d[a][b], c);}dijkstra();cout << cnt[T] << ' ' << sum[T] << endl;return 0;
}

AcWing 1507. 旅行计划

问题描述

  • 问题链接:AcWing 1507. 旅行计划、原题链接

分析

  • 本题相当于给定我们一个无向图,让我们求两点之间最短路径(长度之和),如果存在多条最短路径,输出花费(边权之和)最小的一个。AcWing 1475. 紧急情况是输出点权最小的一个。

  • 使用数组dist记录从起点到每个点的最短路径,使用数组cost记录从起点到每个点的所有最短路径花费(边权之和)最小值。

代码

  • C++
#include <iostream>
#include <cstring>
#include <vector>using namespace std;const int N = 510;int n, m, S, T;
int d[N][N], c[N][N];  // 距离、花费
int dist[N], cost[N], pre[N];  // pre记录路径
bool st[N];void dijkstra() {memset(dist, 0x3f, sizeof dist);memset(cost, 0x3f, sizeof cost);dist[S] = 0, cost[S] = 0;for (int i = 0; i < n; i++) {int t = -1;for (int j = 0; j < n; j++)if (!st[j] && (t == -1 || dist[t] > dist[j]))t = j;st[t] = true;for (int j = 0; j < n; j++)if (dist[j] > dist[t] + d[t][j]) {dist[j] = dist[t] + d[t][j];cost[j] = cost[t] + c[t][j];pre[j] = t;} else if (dist[j] == dist[t] + d[t][j] && cost[j] > cost[t] + c[t][j]) {cost[j] = cost[t] + c[t][j];pre[j] = t;}}
}int main() {cin >> n >> m >> S >> T;memset(d, 0x3f, sizeof d);memset(c, 0x3f, sizeof c);for (int i = 0; i < m; i++) {int a, b, x, y;cin >> a >> b >> x >> y;// 因为要确保存储的是同一条道路的信息,优先满足长度更小// 这是因为要求长度之和最小的,如有相同再考虑花费if (x < d[a][b]) {d[a][b] = d[b][a] = x;c[a][b] = c[b][a] = y;} else if (x == d[a][b] && y < c[a][b]) {d[a][b] = d[b][a] = x;c[a][b] = c[b][a] = y;}}dijkstra();vector<int> path;for (int i = T; i != S; i = pre[i]) path.push_back(i);cout << S;for (int i = path.size() - 1; i >= 0; i--) cout << ' ' << path[i];cout << ' ' << dist[T] << ' ' << cost[T] << endl;return 0;
}

AcWing 1518. 团伙头目

问题描述

  • 问题链接:AcWing 1518. 团伙头目、原题链接

分析

  • 本题相当于给定一个无向图,图中可能有多个连通块,请找出各个连通块,如果连通块中点数大于等于三个,并且边权和大于给定阈值K,输出该联通块中 点权最大的节点以及点的数目。

  • 考虑数据的存储,因为点用字符串表示,因此可以使用哈希表存储图。

  • dfs过程中求出每个连通块,以及连通块中边权之和,因为存储图的时候使用两条有向边表示一条无向边,因此最终求出的边权之和需要除以2

代码

  • C++
#include <iostream>
#include <vector>
#include <algorithm>
#include <unordered_map>using namespace std;typedef pair<string, int> PSI;int n, k;
unordered_map<string, vector<PSI>> g;  // 存储图, 可以有重边, 重边边权也要计算
unordered_map<string, int> total;  // 记录每个人的通话时间
unordered_map<string, bool> st;  // 判重数组int dfs(string ver, vector<string> &nodes) {nodes.push_back(ver);st[ver] = true;int sum = 0;for (auto edge : g[ver]) {sum += edge.second;string cur = edge.first;if (!st[cur]) sum += dfs(cur, nodes);}return sum;
}int main() {cin >> n >> k;for (int i = 0; i < n; i++) {string a, b;int t;cin >> a >> b >> t;g[a].push_back({b, t});g[b].push_back({a, t});total[a] += t;total[b] += t;}vector<PSI> res;  // (帮派老大, 帮派人数)for (auto &item : total) {string ver = item.first;if (st[ver]) continue;  // 说明此人已经在一个帮派中vector<string> nodes;  // 记录帮派中的人int sum = dfs(ver, nodes) / 2;if (nodes.size() > 2 && sum > k) {string boss = nodes[0];for (auto node : nodes)if (total[boss] < total[node])boss = node;res.push_back({boss, (int)nodes.size()});}}sort(res.begin(), res.end());cout << res.size() << endl;for (auto item : res) cout << item.first << ' ' << item.second << endl;return 0;
}

AcWing 1577. 条条大路通罗马

问题描述

  • 问题链接:AcWing 1577. 条条大路通罗马、原题链接

分析

  • 本题为了方便操作,将字符串映射成数字,使用哈希表mp进行映射,这样就可以使用邻接矩阵存储图了。

  • 因为当最大幸福感的路线不唯一,我们需要选择平均幸福感最大的路线,可以转化为选择经过节点更少的路径。

代码

  • C++
#include <iostream>
#include <cstring>
#include <vector>
#include <unordered_map>using namespace std;const int N = 210;int n, m;
int w[N];  // 幸福感
int d[N][N];  // 邻接矩阵存储图
// 最短距离,最短路数量,最大点权(幸福感),最小点数, 最短路径的前一个点
int dist[N], cnt[N], cost[N], sum[N], pre[N];
bool st[N];string city[N];  // 存储城市名称
unordered_map<string, int> mp;  // 城市名->数字void dijkstra() {memset(dist, 0x3f, sizeof dist);dist[1] = 0, cnt[1] = 1;for (int i = 0; i < n; 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 = 1; j <= n; j++) {if (dist[j] > dist[t] + d[t][j]) {dist[j] = dist[t] + d[t][j];cnt[j] = cnt[t];cost[j] = cost[t] + w[j];sum[j] = sum[t] + 1;pre[j] = t;} else if (dist[j] == dist[t] + d[t][j]) {// 最短路径不唯一,选取使人们获得最大幸福感的路线cnt[j] += cnt[t];  // 最短路径条数if (cost[j] < cost[t] + w[j]) {  // 这里是求最大幸福感,注意不等号方向cost[j] = cost[t] + w[j];sum[j] = sum[t] + 1;pre[j] = t;} else if (cost[j] == cost[t] + w[j]) {// 最大幸福感的路线不唯一, 选择平均幸福感最大的路线// 即经过的点数较少的路径if (sum[j] > sum[t] + 1) {sum[j] = sum[t] + 1;pre[j] = t;}}}}}
}int main() {cin >> n >> m >> city[1];mp[city[1]] = 1;for (int i = 2; i <= n; i++) {cin >> city[i] >> w[i];mp[city[i]] = i;}memset(d, 0x3f, sizeof d);for (int i = 0; i < m; i++) {string x, y;int c;cin >> x >> y >> c;int a = mp[x], b = mp[y];d[a][b] = d[b][a] = min(d[a][b], c);}dijkstra();int T = mp["ROM"];// 最小成本的路线数量,最小成本,幸福感,平均幸福感(只取整数部分)cout << cnt[T] << ' ' << dist[T] << ' ' << cost[T] << ' ' << cost[T] / sum[T] << endl;vector<int> path;for (int i = T; i != 1; i = pre[i]) path.push_back(i);cout << city[1];for (int i = path.size() - 1; i >= 0; i--)cout << "->" << city[path[i]];cout << endl;return 0;
}

AcWing 1601. 在线地图

问题描述

  • 问题链接:AcWing 1601. 在线地图、原题链接

分析

  • 按照常理,每条路只有一条记录,因此不需要考虑讨论中的情况。

  • 使用dijkstra求解,如果有多条最短路径,则考虑用时或者经过的路口数即可。

代码

  • C++
#include <iostream>
#include <cstring>
#include <vector>using namespace std;const int N = 510;int n, m, S, T;
// 1: 路程最短的路线; 2: 用时最快的路线
int d1[N][N], d2[N][N];  // 按照常理,每条路只有一条记录
int dist1[N], dist2[N], pre[N];
bool st[N];pair<int, string> dijkstra(int d1[][N], int d2[][N], int type) {memset(dist1, 0x3f, sizeof dist1);memset(dist2, 0x3f, sizeof dist2);memset(st, 0, sizeof st);dist1[S] = 0, dist2[S] = 0;for (int i = 0; i < n; i++) {int t = -1;for (int j = 0; j < n; j++)if (!st[j] && (t == -1 || dist1[t] > dist1[j]))t = j;st[t] = true;for (int j = 0; j < n; j++) {// 线不唯一时考虑的内容// 路程最短的路线: 考虑用时最短// 用时最快的路线: 考虑经过路口最少int w;if (type == 0) w = d2[t][j];else w = 1;if (dist1[j] > dist1[t] + d1[t][j]) {dist1[j] = dist1[t] + d1[t][j];dist2[j] = dist2[t] + w;pre[j] = t;} else if (dist1[j] == dist1[t] + d1[t][j]) {if (dist2[j] > dist2[t] + w) {dist2[j] = dist2[t] + w;pre[j] = t;}}}}vector<int> path;for (int i = T; i != S; i = pre[i]) path.push_back(i);pair<int, string> res;res.first = dist1[T];res.second = to_string(S);for (int i = path.size() - 1; i >= 0; i--)res.second += " -> " + to_string(path[i]);return res;
}int main() {cin >> n >> m;memset(d1, 0x3f, sizeof d1);memset(d2, 0x3f, sizeof d2);for (int i = 0; i < m; i++) {int a, b, t, c, d;cin >> a >> b >> t >> c >> d;if (d1[a][b] > c) d1[a][b] = c, d2[a][b] = d;else if (d1[a][b] == c && d2[a][b] > d) d2[a][b] = d;if (!t) {if (d1[b][a] > c) d1[b][a] = c, d2[b][a] = d;else if (d1[b][a] == c && d2[b][a] > d) d2[b][a] = d;}}cin >> S >> T;auto A = dijkstra(d1, d2, 0);  // 路程最短的路线auto B = dijkstra(d2, d1, 1);  // 用时最快的路线if (A.second != B.second) {printf("Distance = %d: %s\n", A.first, A.second.c_str());printf("Time = %d: %s\n", B.first, B.second.c_str());} else {printf("Distance = %d; Time = %d: %s\n", A.first, B.first, A.second.c_str());}return 0;
}

AcWing 1615. 哈密顿回路

问题描述

  • 问题链接:AcWing 1615. 哈密顿回路、原题链接

分析

  • 一个图是欧拉回路需要满足如下条件:

    (1)起点和终点相同;

    (2)每一步都有边;

    (3)所有点都走到了;

    (4)一共n+1个点。

代码

  • C++
#include <iostream>
#include <cstring>using namespace std;const int N = 210;int n, m;
bool g[N][N];
bool st[N];
int nodes[N * 2];bool check(int cnt) {// 检查条件(1)、(4)if (nodes[0] != nodes[cnt - 1] || cnt != n + 1) return false;// 检查条件(2)memset(st, 0, sizeof st);for (int i = 0; i < cnt - 1; i++) {st[nodes[i]] = true;if (!g[nodes[i]][nodes[i + 1]])return false;}// 检查条件(2)for (int i = 1; i <= n; i++)if (!st[i])return false;return true;
}int main() {cin >> n >> m;while (m--) {int a, b;cin >> a >> b;g[a][b] = g[b][a] = true;}int k;cin >> k;while (k--) {int cnt;cin >> cnt;for (int i = 0; i < cnt; i++) cin >> nodes[i];if (check(cnt)) puts("YES");else puts("NO");}return 0;
}

AcWing 1619. 欧拉路径

问题描述

  • 问题链接:AcWing 1619. 欧拉路径、原题链接

分析

  • 按照题目的要求判断即可。

代码

  • C++
#include <iostream>using namespace std;const int N = 510;int n, m;
bool g[N][N];
bool st[N];
int d[N];int dfs(int u) {st[u] = true;int res = 1;for (int i = 1; i <= n; i++)if (!st[i] && g[u][i])res += dfs(i);return res;
}int main() {cin >> n >> m;for (int i = 0; i < m; i++) {int a, b;cin >> a >> b;g[a][b] = g[b][a] = true;d[a]++, d[b]++;}int cnt = dfs(1);cout << d[1];for (int i = 2; i <= n; i++) cout << ' ' << d[i];cout << endl;if (cnt == n) {int s = 0;for (int i = 1; i <= n; i++)if (d[i] % 2)s++;if (s == 0) puts("Eulerian");else if (s == 2) puts("Semi-Eulerian");else puts("Non-Eulerian");} else {  // 说明图不连通puts("Non-Eulerian");}return 0;
}

AcWing 1624. 地铁地图

问题描述

  • 问题链接:AcWing 1624. 地铁地图、原题链接

分析

  • 为了保证换乘次数最少,我们在建立连边时,不能仅建立相邻可达的站点,这样不方便操作,因此对于每条线路,我们将所有线路上的所有站点之间都连接一条边,边的权重是两站点之间(不含该两站点)的站点数加一。

  • 这样的话,问题就变为了:在所有的最短路中找到边数最少的路线。

  • 本题中最多有100条线路,每条线路最多有100个站台,因此点数最多有一万;一条线路最多有一万条边,因此边数最多一百万条。需要使用堆优化版的dijkstra求解。

  • 另外在输出的时候,还需要输出线路,因此存储边的时候还需要存储边所在的线路。

  • 在使用dijkstra求解最短路的时候,除了常规的判重数组st、距离数组dist,还需要经过的边数数组cnt、线路数组pre,以及如何到达该点的信息数组info

  • 另外还要注意如果是环线的话,我们需要选择较短的一条,如下图:

代码

  • C++
#include <iostream>
#include <queue>
#include <vector>
#include <cstring>#define x first
#define y secondusing namespace std;typedef pair<int, int> PII;const int N = 10010, M = 1000010;int n;  // 线路数
int h[N], e[M], ne[M], w[M], line[M], idx;  // line记录这条边位于哪条线路
int stops[110];  // 存储每个线路站台编号
// 最短距离,最小边数, 最短路径的前一个点
int dist[N], cnt[N], pre[N];
string info[N];  // 到当前点的详细信息
bool st[N];  // dijkstra中判重数组// 增加一条从站台a到站台b的边,经过c站路,位于地铁id号线
void add(int a, int b, int c, int id) {e[idx] = b, w[idx] = c, line[idx] = id, ne[idx] = h[a], h[a] = idx++;
}string get(int x) {string s = to_string(x);s = string(4 - s.size(), '0') + s;return s;
}void dijkstra(int start, int end) {memset(dist, 0x3f, sizeof dist);memset(cnt, 0x3f, sizeof cnt);memset(st, 0, sizeof st);dist[start] = 0;cnt[start] = 0;priority_queue<PII, vector<PII>, greater<PII>> heap;heap.push({0, start});while (heap.size()) {auto t = heap.top();heap.pop();int ver = t.y;if (st[ver]) continue;st[ver] = true;if (ver == end) break;for (int i = h[ver]; ~i; i = ne[i]) {int j = e[i];if (dist[j] > dist[ver] + w[i]) {dist[j] = dist[ver] + w[i];cnt[j] = cnt[ver] + 1;pre[j] = ver;info[j] = "Take Line#" + to_string(line[i]) + " from " + get(ver) + " to " + get(j) + ".";heap.push({dist[j], j});} else if (dist[j] == dist[ver] + w[i]) {  // 存在多条最短if (cnt[j] > cnt[ver] + 1) {cnt[j] = cnt[ver] + 1;pre[j] = ver;info[j] = "Take Line#" + to_string(line[i]) + " from " + get(ver) + " to " + get(j) + ".";}}}}cout << dist[end] << endl;vector<string> path;for (int i = end; i != start; i = pre[i])path.push_back(info[i]);for (int i = path.size() - 1; ~i; i--)printf("%s\n", path[i].c_str());
}int main() {scanf("%d", &n);memset(h, -1, sizeof h);for (int i = 1; i <= n; i++) {  // 路线编号从1开始int m;scanf("%d", &m);for (int j = 0; j < m; j++) scanf("%d", &stops[j]);// 同一条线路上的所有站点之间建立连边for (int j = 0; j < m; j++)for (int k = 0; k < j; k++) {int length;if (stops[0] != stops[m - 1]) length = j - k;else length = min(j - k, m - 1 - j + k);add(stops[k], stops[j], length, i);add(stops[j], stops[k], length, i);}}// 处理询问int m;scanf("%d", &m);while (m--) {int start, end;scanf("%d%d", &start, &end);dijkstra(start, end);}return 0;
}

AcWing 1627. 顶点覆盖

问题描述

  • 问题链接:AcWing 1627. 顶点覆盖、原题链接

分析

  • 使用结构体存储所有边;对于给定的的顶点集合,使用哈希表存储;然后遍历所有边,检查每条边对应的两个顶点是否至少有一个在哈希表中即可。

  • 如果有某条边两个顶点在哈希表中都不存在,输出No,否则输出Yes

代码

  • C++
#include <iostream>
#include <unordered_set>using namespace std;const int N = 10010, M = N;int n, m;struct Edge {int a, b;
} e[M];int main() {ios::sync_with_stdio(false);cin.tie(0);cin >> n >> m;for (int i = 0; i < m; i++) cin >> e[i].a >> e[i].b;int k;cin >> k;while (k--) {int cnt;cin >> cnt;unordered_set<int> S;while (cnt--) {int x;cin >> x;S.insert(x);}bool success = true;for (int i = 0; i < m; i++) if (S.count(e[i].a) == 0 && S.count(e[i].b) == 0) {success = false;break;}if (success) puts("Yes");else puts("No");}return 0;
}

AcWing 1632. 第一次接触

问题描述

  • 问题链接:AcWing 1632. 第一次接触、原题链接

分析

  • 本题直接暴力枚举C、D即可。注意这里四位编码可能是-0000,因此需要使用字符串。可以将字符串映射到数字上即可。

代码

  • C++
#include <iostream>
#include <vector>
#include <algorithm>
#include <unordered_map>using namespace std;const int N = 310;int n, m;
unordered_map<string, int> mp;  // 人物编号->唯一的id
string num[N];  // 唯一的id -> 人物编号
int id;
bool g[N][N];
vector<int> boys, girls;  // 分别存储男孩、女孩对应的idint main() {scanf("%d%d", &n, &m);char as[10], bs[10];for (int i = 0; i < m; i++) {scanf("%s%s", as, bs);string x = as, y = bs;if (x.size() == 5) x = x.substr(1);if (y.size() == 5) y = y.substr(1);if (mp.count(x) == 0) mp[x] = ++id, num[id] = x;if (mp.count(y) == 0) mp[y] = ++id, num[id] = y;int a = mp[x], b = mp[y];g[a][b] = g[b][a] = true;if (as[0] != '-') boys.push_back(a);else girls.push_back(a);if (bs[0] != '-') boys.push_back(b);else girls.push_back(b);}sort(boys.begin(), boys.end());boys.erase(unique(boys.begin(), boys.end()), boys.end());sort(girls.begin(), girls.end());girls.erase(unique(girls.begin(), girls.end()), girls.end());int k;scanf("%d", &k);while (k--) {scanf("%s%s", as, bs);string x = as, y = bs;vector<pair<string, string>> res;vector<int> p = boys, q = boys;if (x[0] == '-') p = girls, x = x.substr(1);if (y[0] == '-') q = girls, y = y.substr(1);int a = mp[x], b = mp[y];for (auto c : p)for (auto d : q)if (c != a && c != b && d != a && d != b && g[a][c] && g[c][d] && g[d][b])res.push_back({num[c], num[d]});sort(res.begin(), res.end());printf("%d\n", res.size());for (auto p : res)printf("%s %s\n", p.first.c_str(), p.second.c_str());}return 0;
}

AcWing 1635. 最大团

问题描述

  • 问题链接:AcWing 1635. 最集团、原题链接

分析

  • 使用邻接矩阵存储图,对于每个读入的顶点集合(存储到vers中),首先检查其中所有的顶点是否全部连通,不是的话说明不是团,否则是团。

  • 接着检查是否为最大团:检查团外的其他顶点 和 团中的每个顶点的连接情况,如果存在某个团外的点和 团中所有点相连,说明不是做大团;否则是最大团。

代码

  • C++
#include <iostream>
#include <cstring>using namespace std;const int N = 210;int n, m;
bool g[N][N];
int vers[N];
bool st[N];bool check_clique(int cnt) {for (int i = 0; i < cnt; i++)for (int j = 0; j < i; j++)if (!g[vers[i]][vers[j]]) return false;return true;
}bool check_maximal(int cnt) {memset(st, 0, sizeof st);for (int i = 0; i < cnt; i++)st[vers[i]] = true;for (int i = 1; i <= n; i++)if (!st[i]) {  // 说明是团外的点bool success = true;  // 是否可以加入点ifor (int j = 0; j < cnt; j++)if (!g[i][vers[j]]) {success = false;break;}if (success) return false;}return true;
}int main() {ios::sync_with_stdio(false);cin.tie(0);cin >> n >> m;for (int i = 0; i < m; i++) {int a, b;cin >> a >> b;g[a][b] = g[b][a] = true;}int k;cin >> k;while (k--) {int cnt;cin >> cnt;for (int i = 0; i < cnt; i++) cin >> vers[i];if (check_clique(cnt)) {if (check_maximal(cnt)) puts("Yes");else puts("Not Maximal");} else puts("Not a Clique");}return 0;
}

AcWing 1639. 拓扑顺序

问题描述

  • 问题链接:AcWing 1639. 拓扑顺序、原题链接

分析

  • 根据给出的拓扑排序的序列,我们记录图中每个点在序列中的位置,记录到数组p中,然后遍历所有的边,对于每条有向边(a, b),必须满足p[a]<p[b],否则不满足拓扑序。

代码

  • C++
#include <iostream>using namespace std;const int N = 1010, M = 10010;int n, m;struct Edge {int a, b;
} e[M];
int p[N];  // 每个点在数组中的位置int main() {cin >> n >> m;for (int i = 0; i < m; i++) cin >> e[i].a >> e[i].b;int k;cin >> k;bool is_first = true;for (int i = 0; i < k; i++) {for (int j = 0; j < n; j++) {int x;cin >> x;p[x] = j;}bool success = true;for (int j = 0; j < m; j++)if (p[e[j].a] > p[e[j].b]) {success = false;break;}if (!success) {if (is_first) is_first = false;else cout << ' ';cout << i;}}cout << endl;return 0;
}

AcWing 1643. 旅行商问题

问题描述

  • 问题链接:AcWing 1643. 旅行商问题、原题链接

分析

  • 首先判断给出的路径是否可行,即是否存在两个相邻的点不存在边,可以使用sum记录路径长度,如果两个相邻的点不存在边的话,将sum置为-1

  • 如果sum!=-1说明这是一条合法路径,然后判断这是否是一个经过所有点的环,需要满足两个条件:(1)经过所有的点;(2)首尾节点相同。

  • 不满足上面两个条件中的任何一个,说明不是一个经过所有点的环,将success置为false表示。

  • 否则如果满足上述两个条件的话,还需要判断经过的点数是不是恰好为n+1,如果是的话说明是简单路径,否则说明是一个经过了所有点但某些点经过多次的环。

代码

  • C++
#include <iostream>
#include <cstring>using namespace std;const int N = 210, INF = 0x3f3f3f3f;int n, m;
int g[N][N];
bool st[N];  // 路径上是否经过该点
int vers[310];int main() {cin >> n >> m;memset(g, 0x3f, sizeof g);for (int i = 0; i < m; i++) {int a, b, c;cin >> a >> b >> c;g[a][b] = g[b][a] = c;}int k;cin >> k;int min_dist = INF, min_id = 0;for (int T = 1; T <= k; T++) {int cnt;cin >> cnt;for (int i = 0; i < cnt; i++) cin >> vers[i];memset(st, 0, sizeof st);int sum = 0;bool success = true;for (int i = 0; i + 1 < cnt; i++) {int a = vers[i], b = vers[i + 1];if (g[a][b] == INF) {sum = -1;success = false;break;}sum += g[a][b];st[a] = true;}for (int i = 1; i <= n; i++)if (!st[i]) {success = false;break;}if (vers[0] != vers[cnt - 1]) success = false;if (sum == -1) printf("Path %d: NA (Not a TS cycle)\n", T);else {if (!success) printf("Path %d: %d (Not a TS cycle)\n", T, sum);else {if (cnt == n + 1) printf("Path %d: %d (TS simple cycle)\n", T, sum);else printf("Path %d: %d (TS cycle)\n", T, sum);if (min_dist > sum) {min_dist = sum;min_id = T;}}}}printf("Shortest Dist(%d) = %d\n", min_id, min_dist);return 0;
}

AcWing 1648. 顶点着色

问题描述

  • 问题链接:AcWing 1648. 顶点着色、原题链接

分析

  • 使用结构体将所有边存储下来,然后对于每个询问,遍历所有边,检查是否存在边的两个顶点颜色相同,如果存在则说明不是合法方案。否则是合法方案,遍历所有顶点,统计顶点的颜色种类即可。

  • 统计颜色种类可以使用哈希表。

代码

  • C++
#include <iostream>
#include <unordered_set>using namespace std;const int N = 10010, M = N;int n, m;
int vers[N];struct Edge {int a, b;
} e[M];int main() {cin >> n >> m;for (int i = 0; i < m; i++) cin >> e[i].a >> e[i].b;int k;cin >> k;while (k--) {for (int i = 0; i < n; i++) cin >> vers[i];bool success = true;for (int i = 0; i < m; i++)if (vers[e[i].a] == vers[e[i].b]) {success = false;break;}if (!success) puts("No");else {unordered_set<int> S;for (int i = 0; i < n; i++) S.insert(vers[i]);printf("%d-coloring\n", S.size());}}return 0;
}

AcWing 1495. 公共自行车管理

问题描述

  • 问题链接:AcWing 1495. 公共自行车管理、原题链接

分析

  • 本题点的编号是0~n,其中0是起点,假设终点是S,则我们想要找到起点到终点的一条路径,这条路径满足:

    (1)距离最短;

    (2)如果有多条最短路径,选取带去自行车最少的路线;

    (3)如果仍有多条路径,选择带回自行车最少的路线。

  • 本题无法在dijkstra的过程中确定需要带去多少自行车。假设s1、s2都可以到达s3,且s1、s2分别比理想状态多了3、4量自行车,我们无法确定应该选哪一个,这是因为s3可能缺少4辆自行车,则此时选择s2更好。

  • 我们首先用dijkstra求出终点起点的最短路径,然后暴力枚举所有最短路径,找到一条带去自行车最少的路径(有多条的话,找到带回自行车最少的路径)。

  • 那么我们如何找到带去自行车和带回自行车的数量呢?可以使用一个变量s表示在当前节点要带回的自行车数量,该节点缺少自行车的话s减小(负数说明需要带自行车,整数说明不需要带),s过程中最小的负数就是需要带去的自行车数量,记为mins。则最终需要带去的自行车数量为sd=abs(min(mins, 0)),因为如果每个站台都不缺少自行车,则mins为正数,此时可以不用带去自行车。

  • 需要带回的自行车数量就是:在终点还剩余的自行车数量ssd之和。

代码

  • C++
#include <iostream>
#include <vector>
#include <cstring>using namespace std;const int N = 510, INF = 0x3f3f3f3f;// 每个车站的最大存车量, 车站总数, 终点, 道路数量
int C, n, S, m;
int c[N];
int g[N][N];
int dist[N];  // 终点到起点的最短路径
bool st[N];vector<int> path, ans;  // path: dfs过程中的路径; ans: 记录最优路径
int send = INF, bring = INF;  // send: 需要带去的行车数量; bring: 需要带回的行车数量void dijkstra() {memset(dist, 0x3f, sizeof dist);dist[S] = 0;for (int i = 0; i <= n; i++) {int t = -1;for (int j = 0; j <= n; j++)if (!st[j] && (t == -1 || dist[j] < dist[t]))t = j;st[t] = true;        for (int j = 0; j <= n; j++)dist[j] = min(dist[j], dist[t] + g[t][j]);}
}// u: 当前考察的节点;
// s: 到达当前节点前需要带回的自行车数量(负数的话说明要从起点带来自行车)
// mins: dfs过程中s的最小值
void dfs(int u, int s, int mins) {if (u) {  // 起点不用考虑s -= (C + 1) / 2 - c[u];mins = min(mins, s);}if (u == S) {  // 到达终点int sd = abs(min(mins, 0));  // 当前考察路径需要带去的自行车数量int bg = s + sd;  // 当前考察路径需要带回的自行车数量if (sd < send) send = sd, bring = bg, ans = path;else if (sd == send && bg < bring) bring = bg, ans = path;}for (int i = 1; i <= n; i++)if (dist[u] == g[u][i] + dist[i]) {path.push_back(i);dfs(i, s, mins);path.pop_back();}
}int main() {cin >> C >> n >> S >> m;memset(g, 0x3f, sizeof g);for (int i = 1; i <= n; i++) cin >> c[i];for (int i = 0; i < m; i++) {int a, b, c;cin >> a >> b >> c;g[a][b] = g[b][a] = min(g[a][b], c);}dijkstra();// 从起点开始搜,找到带去自行车(带回自行车)最少的路径path.push_back(0);dfs(0, 0, 0);cout << send << ' ' << ans[0];for (int i = 1; i < ans.size(); i++)cout << "->" << ans[i];cout << ' ' << bring << endl;return 0;
}

AcWing 1558. 加油站

问题描述

  • 问题链接:AcWing 1558. 加油站、原题链接

分析

  • 这里将m个加油站候选位置的编号变为n+1、n+m,这些候选位置之间也可能有路径,因此在求最短路径的时候也需要考虑。

  • 因为一共最多只有10个候选位置,可以枚举加油站设置的位置,然后检查以此候选位置为加油站位置是否符合题目要求,要求如下:

    (1)所有房屋距离加油站最短距离不超过给定值D

    (2)距离加油站最近的房屋的距离的距离mind要尽可能大;

    (3)所有房屋到加油站的距离之和sumd尽可能小;

  • 另外还要求如果满足上述条件的情况下存在多条答案,则选择编号较小的候选位置。我们在枚举候选位置的时候从小到大枚举即可满足此条件。

代码

  • C++
#include <iostream>
#include <cstring>using namespace std;const int N = 1020, INF = 0x3f3f3f3f;int n, m, k, D;
int g[N][N];
int dist[N];
bool st[N];// 返回位置编号
int get(string s) {if (s[0] == 'G') return n + stoi(s.substr(1));return stoi(s);
}void dijkstra(int S, int &mind, int &sumd) {memset(dist, 0x3f, sizeof dist);memset(st, 0, sizeof st);dist[S] = 0;for (int i = 0; i < n + m; i++) {int t = -1;for (int j = 1; j <= n + m; j++)if (!st[j] && (t == -1 || dist[j] < dist[t]))t = j;st[t] = true;for (int j = 1; j <= n + m; j++)dist[j] = min(dist[j], dist[t] + g[t][j]);}// 查考所有村庄到当前候选位置的距离for (int i = 1; i <= n; i++)if (dist[i] > D) {mind = -INF;return;}mind = INF, sumd = 0;for (int i = 1; i <= n; i++) {mind = min(mind, dist[i]);sumd += dist[i];}
}int main() {cin >> n >> m >> k >> D;memset(g, 0x3f, sizeof g);for (int i = 0; i < k; i++) {string a, b;int z;cin >> a >> b >> z;int x = get(a), y = get(b);g[x][y] = g[y][x] = min(g[x][y], z);}// res: 选择的候选点编号; maxd: 最短距离的最大值; sumd: 对应的所有房屋到加油站的距离之和int res = -1, mind = 0, sumd = INF;for (int i = n + 1; i <= n + m; i++) {int d1, d2;  // 选择i为加油站位置对应的mind, sumddijkstra(i, d1, d2);if (d1 > mind) mind = d1, sumd = d2, res = i;else if (d1 == mind && d2 < sumd) sumd = d2, res = i;}if (res == -1) puts("No Solution");else printf("G%d\n%.1lf %.1lf\n", res - n, (double)mind, (double)sumd / n + 1e-8);return 0;
}

AcWing 1562. 微博转发

问题描述

  • 问题链接:AcWing 1562. 微博转发、原题链接

分析

  • 如果x关注y,则让yx连接一条边。本题相当于从某个点出发宽度优先遍历一定的层数,将遍历到的点数输出即可。

代码

  • C++
#include <iostream>
#include <cstring>
#include <queue>using namespace std;const int N = 1010, M = 100010;int n, l;
int h[N], e[M], ne[M], idx;
bool st[N];void add(int a, int b) {e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}int bfs(int start) {memset(st, 0, sizeof st);queue<int> q;q.push(start);st[start] = true;int cnt = 0;for (int step = 0; step < l; step++) {int sz = q.size();cnt += sz;for (int i = 0; i < sz; i++) {int t = q.front();q.pop();for (int j = h[t]; ~j; j = ne[j]) {int k = e[j];if (!st[k]) {q.push(k);st[k] = true;}}}}cnt += q.size() - 1;return cnt;
}int main() {scanf("%d%d", &n, &l);memset(h, -1, sizeof h);for (int i = 1; i <= n; i++) {int cnt;scanf("%d", &cnt);while (cnt--) {int id;scanf("%d", &id);add(id, i);  // i关注id}}int k;scanf("%d", &k);while (k--) {int x;scanf("%d", &x);printf("%d\n", bfs(x));}return 0;
}

【PAT】06 图论相关推荐

  1. 【第23期】令人舒心又伤脑的12张数学原理动图!你能看懂几个

    1.被简单证明的勾股定理 给三角形加上一点厚度.从面积问题,跳转到了具象的体积问题. 2.勾股定理的面积证明法 It s a long story--慢慢看. 3.周长和直径的π点小事 4.圆的面积= ...

  2. 《天才引导的历程》| 西安交通大学送给准大一新生的礼物

    8月8日西安交通大学发出了陕西省今年第一封高考录取通知书!  通知书的主人是家住纺织城的刘同学,她今年高考取得了670分的好成绩,录取到交大强基计划数学类专业. 收到录取通知书的同时,刘同学还收到了一 ...

  3. PAT甲级1139 First Contact (30 分):[C++题解] 图论、暴力枚举两个点、hash映射

    文章目录 题目分析 题目链接 题目分析 来源:acwing 题目分析: 图论模拟题. 给定暗恋的两个人A 和B,需要寻找一对C 和D ,满足:A和C是朋友,C和D是朋友,D和B是朋友.而且A.C同性别 ...

  4. PAT甲级1076 Forwards on Weibo (30 分) :[C++题解]图论、bfs

    文章目录 题目分析 题目来源 题目分析 来源:acwing 分析: BFS如何搜前k层?统计前k层的点数. ac代码 #include<bits/stdc++.h> using names ...

  5. PAT甲级1154 Vertex Coloring :[C++题解]图论、模拟、结构体存边

    文章目录 题目分析 题目链接 题目分析 来源:acwing 分析: 这题的边不用邻接矩阵.邻接表来存,仅仅用结构体来存即可,结构体正好存边的两个端点,我们只要遍历每条边,判断端点的颜色是否相同即可. ...

  6. PAT甲级1150 Travelling Salesman Problem:[C++题解]旅行商问题、图论

    文章目录 题目分析 题目链接 题目分析 来源:acwing 分析: 旅行商问题:访问每个城市并回到原城市的最短路. 思路: 1)判断相邻两点有无距离(NA):2)每个点是否都能到:3)是否是回路:4) ...

  7. PAT甲级1146 Topological Order :[C++题解]拓扑排序、结构体存边、图论

    文章目录 题目分析 题目链接 题目分析 来源:acwing 拓扑排序是对有向无环图的顶点的一种排序,它使得若存在一条从顶点A到顶点B的路径A→B,则在排序中顶点B出现在顶点A的后面. 根据概念,可以想 ...

  8. PAT甲级1142 Maximal Clique :[C++题解]图论、最大团、枚举

    文章目录 题目分析 题目链接 题目分析 来源:acwing 分析: 团:团是顶点的集合,满足该集合中任意两顶点之间都有边. 判断是不是团:所有点是否有边 判断最大团:是否可以加一个额外点,使得所有点之 ...

  9. PAT甲级1134 Vertex Cover :[C++题解]顶点覆盖、图论、用结构体存边,bool数组判断

    文章目录 题目分析 题目链接 题目分析 来源:acwing 分析:首先把所有的边存储下来,使用结构体,结构体中保存两个端点. 然后对于每次询问,将出现过的顶点标志为true放在st数组中,然后遍历所有 ...

最新文章

  1. 售前工程师的成长(五)---一个老员工的经验之谈
  2. python正规教育机构-长沙python培训机构哪家好
  3. scala中的构造方法和伴生对象
  4. Mono,CLR,.net,Net Framework之间的关系
  5. PAZU 是4Fang 为配合“四方在线”软件于2004年开发的WEB打印控件,适用于各种WEB软件项目的打印。...
  6. 中如何计算工龄_在Substrate中如何计算交易权重
  7. 阿里员工的这则控诉,在圈内火了!
  8. 离散元 python_离散元在土木工程领域的应用前景如何?
  9. CentOS+nginx+uwsgi+Python 多站点环境搭建
  10. i5功耗最低的cpu_近年最大飞跃 Intel第11代低功耗酷睿处理器官宣:集显2倍、AI乘4...
  11. DotNetNuke 04.05.05 安装
  12. sqlite 表与表之间的关系_第33章 Django多表关系之一对一
  13. 安装Linux无盘工作站
  14. 华为云计算认证含金量怎么样?
  15. (BFS/状态压缩)HDU 5025 Saving Tang Monk
  16. 运筹说 第19期 | 线性规划经典例题讲解
  17. 初生牛犊不怕虎 —— 独自背井离乡
  18. 【小程序源码】视频壁纸支持多种分类短视频另外也有静态壁纸
  19. linux设备模型详解
  20. DevOps-jenkins-流水线优化

热门文章

  1. 企业微信怎么扫描名片加好友?企业微信名片如何统计好友添加情况?
  2. 开放科研:数据科学场景下如何让研究更加开放?
  3. Msfvenom编码免杀技术实现免杀实战
  4. 计算机综合评价论文,关于模糊综合评价记忆计算机英语论文
  5. 小伙因家人“催催催” 欲轻生 民警苦劝将其救下
  6. nico和niconiconi
  7. PAT甲级刷题笔记(4)
  8. 运营管理整改报告范文_快递整改报告怎么写
  9. python读取pdf文件_深入学习python解析并读取PDF文件内容的方法
  10. 字母排序 字符串跟字符串比较大小 字符串跟数字比较大小