PAT甲级刷题记录-(AcWing)-Day07图论(6题)和树(2题)
课程来源AcWing
其中AcWing中的题目为翻译好的中文题目
刷题列表
1053 Path of Equal Weight
- assign to 指定给,分配到
解析
- 使用邻接矩阵来存储边,更加直观
- 因为路径的长度是计算到叶子节点的,所以在遇到叶子节点的时候判断一下即可。
注意点
s + w[i]
要在递归中修改,一开始在外面写成s+=w[i]
会导致后面同一个节点的孩子传入的时候s
累加的越来越大。
#include <iostream>
#include <cstring>
#include <vector>
#include <algorithm>
const int N = 110;
using namespace std;
int n, m, S, w[N];
bool edge[N][N];
vector<vector<int>> res;
void dfs(int u, int s, vector<int> &path) {
cout << "s = " << s << endl;
bool is_leaf = true;
// 1. 判断是否是叶子
for (int i = 0; i < n; ++i) {
if (edge[u][i]) {
is_leaf = false;
break;
}
}
if (is_leaf) {
// 是叶子的话判断权值是否为S
if (s == S) res.push_back(path);
} else {
// 不是叶子就遍历下面的节点
for (int i = 0; i < n; ++i) {
if (edge[u][i]) {
path.push_back(w[i]);
dfs(i, s + w[i], path);
path.pop_back();
}
}
}
}
int main() {
cin >> n >> m >> S;
for (int i = 0; i < n; ++i) {
cin >> w[i];
}
while (m--) {
int id, k;
cin >> id >> k;
while (k--) {
int node;
cin >> node;
edge[id][node] = true;
}
}
vector<int> path = {
w[0]};
dfs(0, w[0], path);
sort(res.begin(), res.end(), greater<vector<int>>());
for (auto &a:res) {
cout << a[0];
for (int i = 1; i < a.size(); ++i) {
cout << " " << a[i];
}
cout << endl;
}
return 0;
}
1094 The Largest Generation
英语单词
解析
- 求每层的节点数量先想到的应该是宽搜, 在搜索的过程中存下当前的节点即可
- 也可以递归求深度,然后用数组记录当前深度的值, 这里要注意到节点是从
1
开始的,因此在遍历是否存在孩子的时候要让i = 1; i <= n
注意点
自己写了个用dfs递归求深度的,可以通过PAT
扫描二维码关注公众号,回复:
15507006 查看本文章
#include <iostream>
#include <algorithm>
#include <cstring>
const int N = 110;
using namespace std;
int n, m;
bool edge[N][N];
int cnt[N], max_depth;
void dfs(int u, int depth) {
max_depth = max(depth, max_depth);
cnt[depth]++;
for (int i = 1; i <= n; ++i) {
if (edge[u][i]) {
dfs(i, depth + 1);
}
}
}
int main() {
cin >> n >> m;
while (m--) {
int id, k;
cin >> id >> k;
while (k--) {
int node;
cin >> node;
edge[id][node] = true;
}
}
dfs(1, 1);
int res = 0, index = 0;
for (int i = 1; i <= max_depth; ++i) {
if (cnt[i] > res) {
res = cnt[i];
index = i;
}
}
cout << res << " " << index << endl;
return 0;
}
使用宽度遍历的做法
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
const int N = 110;
using namespace std;
int n, m;
bool edge[N][N];
vector<int> level[N];
int main() {
cin >> n >> m;
while (m--) {
int id, k;
cin >> id >> k;
while (k--) {
int node;
cin >> node;
edge[id][node] = true;
}
}
int depth = 1;
level[1].push_back(1);
while (!level[depth].empty()) {
for (auto &node:level[depth]) {
for (int i = 1; i <= n; ++i) {
if (edge[node][i]) {
level[depth + 1].push_back(i);
}
}
}
depth++;
}
int res = 0, idx;
for (int i = 1; i <= n; ++i) {
if (level[i].size() > res) {
idx = i;
res = level[i].size();
}
}
cout << res << " " << idx << endl;
return 0;
}
1003 Emergency
英语单词
- scattered 分散的,混乱的
注意点
使用朴素dijkstra算法, 在遍历的过程中记录下每个节点的最短路径条数 和 最大救援人数, 也就是新开两个数组cnt[N], sum[N]
来记录。
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 520;
int edge[N][N], dist[N];
int people[N]; // 每个城市的救援人数
int n, m, c1, c2; //c1为源点,c2为目标点
int cnt[N]; // 到每个节点的最短路径个数
int sum[N]; // 每个节点的最大救援人数
bool visit[N]; // 节点是否访问
void dijkstra() {
dist[c1] = 0;
cnt[c1] = 1;
sum[c1] = people[c1];
for (int i = 0; i < n; ++i) {
int t = -1;
for (int j = 0; j < n; ++j)
if (!visit[j] && (t == -1 || dist[t] > dist[j])) t = j;
visit[t] = true;
for (int j = 0; j < n; ++j) {
if (dist[j] > dist[t] + edge[t][j]) {
dist[j] = dist[t] + edge[t][j];
cnt[j] = cnt[t];
sum[j] = sum[t] + people[j];
} else if (dist[j] == dist[t] + edge[t][j]) {
cnt[j] += cnt[t];
sum[j] = max(sum[j], sum[t] + people[j]);
}
}
}
}
int main() {
cin >> n >> m >> c1 >> c2;
// 初始化边和权重值
memset(dist, 0x3f, sizeof(dist));
memset(edge, 0x3f, sizeof(edge));
for (int i = 0; i < n; ++i) {
cin >> people[i];
}
while (m--) {
int a, b, c;
cin >> a >> b >> c;
edge[a][b] = edge[b][a] = c;
}
dijkstra();
cout << cnt[c2] << " " << sum[c2] << endl;
return 0;
}
1030 Travel Plan
英语单词
- destination 目的地
注意点
与上一题类似, 在需要输出路径的题目里可以使用一个pre[N]
来记录前面的节点
#include <iostream>
#include <cstring>
#include <vector>
using namespace std;
const int N = 520;
int edge[N][N], cost[N][N], dist[N];
bool visit[N];
int pre[N];
int sum[N];
int n, m, s, d;
void dijkstra() {
memset(dist, 0x3f, sizeof dist);
memset(sum, 0x3f, sizeof sum);
dist[s] = 0;
sum[s] = 0;
for (int i = 0; i < n; ++i) {
int t = -1;
for (int j = 0; j < n; ++j) {
if (!visit[j] && (t == -1 || dist[t] > dist[j])) t = j;
}
visit[t] = true;
for (int j = 0; j < n; ++j) {
if (dist[j] > dist[t] + edge[t][j]) {
dist[j] = dist[t] + edge[t][j];
sum[j] = sum[t] + cost[t][j];
pre[j] = t;
} else if (dist[j] == dist[t] + edge[t][j]) {
// 然后j的花费比较大,就更新
if (sum[j] > sum[t] + cost[t][j]) {
sum[j] = sum[t] + cost[t][j];
pre[j] = t;
}
}
}
}
}
int main() {
memset(edge, 0x3f, sizeof(edge));
memset(cost, 0x3f, sizeof(cost));
cin >> n >> m >> s >> d;
while (m--) {
int c1, c2, distance, x;
cin >> c1 >> c2 >> distance >> x;
edge[c1][c2] = edge[c2][c1] = distance;
cost[c1][c2] = cost[c2][c1] = x;
}
dijkstra();
vector<int> res;
for (int i = d; i != s; i = pre[i]) {
res.push_back(i);
}
cout << s;
for (int i = res.size() - 1; i >= 0; i--) {
cout << " " << res[i];
}
cout << " " << dist[d] << " " << sum[d];
return 0;
}
!!! 1034 Head of a Gang
英语单词
解析
- 用了很多
std
的操作,因为在图论这边,所以使用了宽搜来判断连通性,其实也可以用并查集做 - 这边在宽搜的过程中使用
visit
记录是否访问过,使用nodes
记录同一帮派的人 - 因为记录边的时候
a,b
和b,a
的记录都放进去了,在for (auto edge:map[ver]) {
遍历的时候会每条边走两次,所以最后的sun
要除以2
#include <iostream>
#include <cstring>
#include <vector>
#include <unordered_map>
#include <algorithm>
using namespace std;
const int N = 1010;
int n, k;
unordered_map<string, vector<pair<string, int>>> map;
unordered_map<string, int> total;
unordered_map<string, bool> visit;
int dfs(string ver, vector<string> &nodes) {
visit[ver] = true;
nodes.push_back(ver);
int sum = 0;
for (auto edge:map[ver]) {
sum += edge.second;
string cur = edge.first;
if (!visit[cur]) sum += dfs(cur, nodes);
}
return sum;
}
int main() {
cin >> n >> k;
for (int i = 0; i < n; ++i) {
string name1, name2;
int time;
cin >> name1 >> name2 >> time;
map[name1].push_back({
name2, time});
map[name2].push_back({
name1, time});
total[name1] += time;
total[name2] += time;
}
vector<pair<string, int>> res;
for (auto &item:total) {
string ver = item.first;
vector<string> nodes;
int sum = dfs(ver, nodes) / 2;
if (nodes.size() > 2 && sum > k) {
string boss = nodes[0];
for (auto people:nodes) {
if (total[people] > total[boss])
boss = people;
}
res.push_back({
boss, nodes.size()});
}
}
sort(res.begin(), res.end());
cout << res.size() << endl;
for (auto item:res) {
cout << item.first << " " << item.second << endl;
}
return 0;
}
按照自己的思路又写了一遍
一开始sum那边忘记除以2了, 导致测试点2过不去
因为在计算total[n]的时候,A给B打电话会让total[A]
增加t
,total[B]
也增加t
,在map
中会记录为A
给B
打了t
分钟,同时B
给A
打了t
分钟, 因此在dfs
的时候遍历完A
还会遍历到B
,当他们的total
都加入到sum
的时候就会导致结果为2t
,而实际上他们只打了t
分钟
#include <iostream>
#include <unordered_map>
#include <vector>
#include <algorithm>
using namespace std;
const int N = 1010;
int n, k;
unordered_map<string, vector<pair<string, int>>> map;
unordered_map<string, int> total;
unordered_map<string, bool> visit;
int dfs(string node, vector<string> &members) {
int sum = total[node];
// 记录当前的节点
visit[node] = true;
members.push_back(node);
// 遍历跟node有打过电话的人
for (auto &item:map[node]) {
string name = item.first;
if (!visit[name]) sum += dfs(name, members);
}
return sum;
}
int main() {
cin >> n >> k;
for (int i = 0; i < n; ++i) {
string name1, name2;
int time;
cin >> name1 >> name2 >> time;
map[name1].push_back({
name2, time});
map[name2].push_back({
name1, time});
total[name1] += time;
total[name2] += time;
}
vector<pair<string, int>> res;
for (auto &item:total) {
// 遍历每个节点,看看其是否在一个连通图里面
string name = item.first;
if (!visit[name]) {
// 当前的节点还没被访问过,可能是一个新的gang,进行遍历,计算总数
vector<string> members;
int sum = dfs(name, members) / 2;
if (sum > k && members.size() > 2) {
// 满足团队的定义, 找团队里的boss, 也就是通话时间最长的
string boss = members[0];
for (auto member:members) {
if (total[member] > total[boss]) {
boss = member;
}
}
// boss 里面记录的是当前团队里面通话时间最长的老大
res.push_back({
boss, members.size()});
}
}
}
cout << res.size() << endl;
sort(res.begin(), res.end());
for (auto &c:res) {
cout << c.first << " " << c.second << endl;
}
return 0;
}
1087 All Roads Lead to Rome
注意点
很有意思的一道题,把前面的很多知识点融合了起来
目前图论题考的核心都是dijkstra
算法,只不过在这个过程中增加了比较的对象,需要记录的东西,以及字符串的映射关系等等.
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#include <unordered_map>
using namespace std;
const int N = 210;
int n, k;
string city[N]; // 每个城市的名字
unordered_map<string, int> map; // 每个城市的名字与下标的对应关系
int happiness[N]; // 每个城市的幸福感指数
int edge[N][N];
int dist[N], cnt[N], max_happiness[N], sum[N], pre[N]; // 最短路径, 相同的最短路径条数,幸福感和,经过的点的个数(反应平均幸福感)
bool visit[N];
void dijkstra() {
dist[1] = 0, cnt[1] = 1, max_happiness[1] = 0;
for (int i = 0; i < n; ++i) {
int t = -1;
for (int j = 1; j <= n; ++j) {
if (!visit[j] && (t == -1 || dist[t] > dist[j])) t = j;
}
visit[t] = true;
for (int j = 1; j <= n; ++j) {
// 用t来更新权重
if (dist[j] > dist[t] + edge[t][j]) {
dist[j] = dist[t] + edge[t][j];
cnt[j] = cnt[t];
sum[j] = sum[t] + 1;
max_happiness[j] = max_happiness[t] + happiness[j];
pre[j] = t;
} else if (dist[j] == dist[t] + edge[t][j]) {
cnt[j] += cnt[t];
// If such a route is not unique, the one with the maximum happiness will be recommanded
if (max_happiness[j] < max_happiness[t] + happiness[j]) {
max_happiness[j] = max_happiness[t] + happiness[j];
pre[j] = t;
sum[j] = sum[t] + 1;
} else if (max_happiness[j] == max_happiness[t] + happiness[j]) {
// 选择平均幸福指数最大的,也就是经过的节点sum[]最少的
if (sum[j] > sum[t] + 1) {
sum[j] = sum[t] + 1;
pre[j] = t;
}
}
}
}
}
}
int main() {
memset(edge, 0x3f, sizeof(edge));
memset(dist, 0x3f, sizeof(dist));
cin >> n >> k;
string start;
cin >> start;
city[1] = start;
map[start] = 1;
for (int i = 2; i <= n; ++i) {
cin >> city[i] >> happiness[i];
map[city[i]] = i;
}
for (int i = 0; i < k; ++i) {
string name1, name2;
int x;
cin >> name1 >> name2 >> x;
int idx1, idx2;
idx1 = map[name1];
idx2 = map[name2];
edge[idx1][idx2] = edge[idx2][idx1] = min(edge[idx1][idx2], x);
}
dijkstra();
// the number of different routes with the least cost, the cost, the happiness, and the average happiness (take the integer part only) of the recommanded route
int index = map["ROM"];
cout << cnt[index] << " " << dist[index] << " " << max_happiness[index] << " " << max_happiness[index] / sum[index]
<< endl;
vector<string> res;
for (int i = index; i != map[start]; i = pre[i]) {
res.push_back(city[i]);
}
cout << start;
for (int i = res.size() - 1; i >= 0; i--) {
cout << "->" << res[i];
}
cout << endl;
return 0;
}
1111 Online Map
英语单词
- streets intersections 街道交叉路口/十字路口
解析
使用两次迪杰斯特拉即可。
注意点
写题目的时候遇到几个小坑
- 因为本题要使用两次
dijkstra
算法,这里我只想到了每次用的时候要重置两个dist
,但是忘记重置visit
矩阵了,就会导致在第二次算法中得不到更新 street += " -> " + to_string(path[i]);
失误写成了i
小错误
#include <iostream>
#include <vector>
#include <cstring>
using namespace std;
const int N = 520;
int n, m, S, D;
int edge[N][N];
int time_cost[N][N];
int dist1[N], dist2[N], pre[N];
bool visit[N];
pair<int, string> dijkstra(int type) {
// type = 1 先考虑距离短(dist1),再考虑时间短(dist2)
// type = 0 先考虑时间短(dist1),再考虑节点最少(dist2)
memset(dist1, 0x3f, sizeof(dist1));
memset(dist2, 0x3f, sizeof(dist2));
memset(visit, 0, sizeof(visit));
dist1[S] = 0, dist2[S] = 0;
for (int i = 0; i < n; ++i) {
int t = -1;
for (int j = 0; j < n; ++j) {
if (!visit[j] && (t == -1 || dist1[t] > dist1[j]))
t = j;
}
visit[t] = true;
for (int j = 0; j < n; ++j) {
int w1, w2;
if (type == 1) w1 = edge[t][j], w2 = time_cost[t][j];
else w1 = time_cost[t][j], w2 = 1;
if (dist1[j] > dist1[t] + w1) {
dist1[j] = dist1[t] + w1;
dist2[j] = dist2[t] + w2;
pre[j] = t;
} else if (dist1[j] == dist1[t] + w1) {
if (dist2[j] > dist2[t] + w2) {
dist2[j] = dist2[t] + w2;
pre[j] = t;
}
}
}
}
int res = dist1[D];
vector<int> path;
for (int i = D; i != S; i = pre[i]) {
path.push_back(i);
}
string street = to_string(S);
for (int i = path.size() - 1; i >= 0; i--) {
street += " -> " + to_string(path[i]);
}
pair<int, string> a = {
res, street};
return a;
}
int main() {
cin >> n >> m;
memset(edge, 0x3f, sizeof(edge));
memset(time_cost, 0x3f, sizeof(time_cost));
for (int i = 0; i < m; ++i) {
int v1, v2, flag, length, t;
cin >> v1 >> v2 >> flag >> length >> t;
edge[v1][v2] = min(edge[v1][v2], length);
time_cost[v1][v2] = min(t, time_cost[v1][v2]);
if (!flag) {
edge[v2][v1] = min(length, edge[v2][v1]);
time_cost[v2][v1] = min(t, time_cost[v2][v1]);
}
}
cin >> S >> D;
pair<int, string> res_dis = dijkstra(1);
pair<int, string> res_time = dijkstra(0);
if (res_dis.second == res_time.second) {
printf("Distance = %d; Time = %d: %s", res_dis.first, res_time.first, res_dis.second.c_str());
} else {
printf("Distance = %d: %s\n", res_dis.first, res_dis.second.c_str());
printf("Time = %d: %s", res_time.first, res_time.second.c_str());
}
return 0;
}
1122 Hamiltonian Cycle
判断回路从一下角度出发即可
- 第一个点和最后一个点相同
- 每个点直接都有边
- 每个顶点都被访问到
- 节点的数量刚好为
n+1
(防止有重复节点)
注意点
- PAT上有一个点卡在了
int nodes[N*2];
要开两倍节点的大小才行
#include <iostream>
#include <cstring>
using namespace std;
const int N = 220;
int n, m, k;
bool edge[N][N], visit[N];
int nodes[N*2];
bool check(int cnt) {
memset(visit, 0, sizeof(visit));
if (nodes[1] != nodes[cnt - 1]) return false; // 回路第一个节点和最后一个节点应该相同
for (int i = 1; i < cnt; ++i) {
visit[nodes[i]] = true; // 遍历的过程中记录下使用到的节点
if (!edge[nodes[i]][nodes[i + 1]]) return false; //回路中每个节点之间应该有边
}
// 判断是否用到了所有的点
for (int i = 1; i <= cnt; ++i) {
if (!visit[i]) return false;
}
return cnt != n + 1; // 回路中的点应该刚好为n+1个
}
int main() {
cin >> n >> m;
while (m--) {
int a, b;
cin >> a >> b;
edge[a][b] = edge[b][a] = true;
}
cin >> k;
while (k--) {
int t;
cin >> t;
for (int i = 1; i <= t; ++i) {
cin >> nodes[i];
}
if (check(t)) puts("YES");
else puts("NO");
}
return 0;
}