第六章 图论
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 second
using 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; // 分别存储男孩、女孩对应的id
int 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; // 是否可以加入点i
for (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
为正数,此时可以不用带去自行车。 -
需要带回的自行车数量就是:在终点还剩余的自行车数量
s
和sd
之和。
代码
- 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, sumd
dijkstra(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
,则让y
向x
连接一条边。本题相当于从某个点出发宽度优先遍历一定的层数,将遍历到的点数输出即可。
代码
- 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;
}