Jerry @DOA&INPAC, SJTU

Working out everything from the first principles.

导航

PAT甲级题分类汇编——图

本文为PAT甲级分类汇编系列文章。

 

图,就是层序遍历和Dijkstra这一套,#include<queue> 是必须的。

题号 标题 分数 大意 时间
1072 Gas Station 30 最短距离最大,距离不超限 200ms
1076 Forwards on Weibo 30 一定层数的转发计数 3000ms
1079 Total Sales of Supply Chain 25 计算供应链总销售额 250ms
1087 All Roads Lead to Rome 30 复杂权重的最短路径问题 200ms
1090 Highest Price in Supply Chain 25 供应链最高价格 200ms
1091 Acute Stroke 30 超过阈值的空间体积之和 600ms

图这一类题的分值都比较大,因为代码量大,同时时间要么短要么长,不是标准的400ms。至于3000ms的1076,是一道简单题,应该是出题老师一不小心多打了一个0。

30分题肯定要做,这次就只做4道30分题:1072、1076、1087和1091。

 

1072:

这道题的输入数据是很典型的,算法以Dijkstra为核心,之后再加上一点最小距离、范围、平均距离等简单的东西,不难。

不知是第几次写Dijkstra了,每次实现都不太一样。之前写过一篇Dijkstra算法与堆,现在看来,算法都不是很好。这一次经过深思熟虑后,我将下面这种写法固定下来:

  1 #include <iostream>
  2 #include <iomanip>
  3 #include <sstream>
  4 #include <vector>
  5 #include <queue>
  6 #include <limits>
  7 
  8 struct Road
  9 {
 10     Road(int dest, int dist)
 11         : dest(dest), dist(dist) { }
 12     int dest;
 13     int dist;
 14 };
 15 
 16 struct Node
 17 {
 18     int dist = std::numeric_limits<int>::max();
 19     std::vector<Road> roads;
 20 };
 21 
 22 struct Coll
 23 {
 24     Coll(int index, int dist)
 25         : index(index), dist(dist) { }
 26     int index;
 27     int dist;
 28 };
 29 
 30 struct Comp
 31 {
 32     static void init(std::vector<Node>& _nodes)
 33     {
 34         nodes = &_nodes;
 35     }
 36     static std::vector<Node>* nodes;
 37     bool operator()(Coll i, Coll j)
 38     {
 39         return (*nodes)[i.index].dist > (*nodes)[j.index].dist;
 40     }
 41 };
 42 
 43 std::vector<Node>* Comp::nodes = nullptr;
 44 
 45 int get_node(int _offset)
 46 {
 47     std::string s;
 48     std::stringstream ss;
 49     std::cin >> s;
 50     ss << s;
 51     if (s[0] == 'G')
 52     {
 53         ss.get();
 54         int i;
 55         ss >> i;
 56         --i;
 57         i += _offset;
 58         return i;
 59     }
 60     else
 61     {
 62         int i;
 63         ss >> i;
 64         --i;
 65         return i;
 66     }
 67 }
 68 
 69 int main()
 70 {
 71     int num_house, num_station, num_road, range;
 72     std::cin >> num_house >> num_station >> num_road >> range;
 73     std::vector<Node> nodes(num_house + num_station);
 74     Comp::init(nodes);
 75     for (int i = 0; i != num_road; ++i)
 76     {
 77         auto n0 = get_node(num_house), n1 = get_node(num_house);
 78         int dist;
 79         std::cin >> dist;
 80         nodes[n0].roads.emplace_back(n1, dist);
 81         nodes[n1].roads.emplace_back(n0, dist);
 82     }
 83     int best = -1;
 84     int minimum = 0;
 85     int total = std::numeric_limits<int>::max();
 86     auto house_begin = nodes.begin();
 87     auto house_end = nodes.begin() + num_house;
 88     auto station_begin = nodes.begin() + num_house;
 89     auto station_end = nodes.end();
 90     for (auto station = station_begin; station != station_end; ++station)
 91     {
 92         std::priority_queue<Coll, std::vector<Coll>, Comp> queue;
 93         for (auto& node : nodes)
 94             node.dist = std::numeric_limits<int>::max();
 95         station->dist = 0;
 96         queue.emplace(station - nodes.begin(), 0);
 97         while (!queue.empty())
 98         {
 99             auto coll = queue.top();
100             queue.pop();
101             auto& node = nodes[coll.index];
102             if (node.dist != coll.dist)
103                 continue;
104             for (const auto& r : node.roads)
105             {
106                 if (node.dist + r.dist < nodes[r.dest].dist)
107                 {
108                     nodes[r.dest].dist = node.dist + r.dist;
109                     queue.emplace(r.dest, nodes[r.dest].dist);
110                 }
111             }
112         }
113         int m = std::numeric_limits<int>::max();
114         int t = 0;
115         bool bad = false;
116         for (auto house = house_begin; house != house_end; ++house)
117         {
118             if (house->dist > range)
119                 bad = true;
120             t += house->dist;
121             if (house->dist < m)
122                 m = house->dist;
123         }
124         if (bad)
125             continue;
126         if (m > minimum)
127         {
128             minimum = m;
129             best = station - nodes.begin();
130             total = t;
131         }
132         else if (m == minimum && t < total)
133         {
134             best = station - nodes.begin();
135             total = t;
136         }
137     }
138     if (best == -1)
139     {
140         std::cout << "No Solution";
141         return 0;
142     }
143     best = best - num_house + 1;
144     std::cout << 'G' << best << std::endl;
145     std::cout.setf(std::ios::fixed);
146     std::cout << std::setprecision(1);
147     std::cout << (double)minimum << ' ' << (double)total / num_house;
148 }

Dijkstra算法的时间复杂度关键取决于“找到距离已收集顶点最近的顶点”这步操作的复杂度。线性肯定是不好的,用优先队列是更好的方法。然而,当你把一个顶点加入到队列中以后,在它弹出之前,这个顶点的距离可能变得更近,即会改变它在堆中应该放的位置,为此需要重排,这样时间复杂度就不划算了。我的新方法是,在优先队列中存储结构体,包括顶点下标与插入优先队列时它的距离。弹出时,将这个距离与顶点对象中维护的距离相比较,如果不相等,说明这个弹出已经过时了,只是之前受限于结构无法删除,但现在可以忽略。由于优先队列以结构体中的距离为键值,此顶点在之前肯定已经被处理过了,所以可以名正言顺地忽略它。这样就可以不需要排序、查找等乱七八糟操作,同时顶点对象中也不再需要表示是否已收集的变量了。

 

1087:

在Dijkstra算法的基础上,这道题主要增加了对考虑相等情况的要求。在cost同为最小的情况下,看第二键值happiness,在后者同为最大的情况下,选择更大的平均happiness,也就是看路径长度哪个更短。由于最优的选择条件依赖于整条路径,不能由已收集的部分得出,因此必须维护所有可能的路径,跟之前PBMC的题类似,我记得那道题卡了我好久。

维护路径的过程中,如果出现相等长度的情况(这道题中是cost),路径要合并;如果有更短路径,就不要之前维护的路径,而从当前处理的节点的路径生成。

  1 #include <iostream>
  2 #include <string>
  3 #include <vector>
  4 #include <queue>
  5 #include <map>
  6 #include <limits>
  7 
  8 struct Route
  9 {
 10     Route() = default;
 11     Route(int dest, int dist)
 12         : dest(dest), dist(dist) { }
 13     int dest;
 14     int dist;
 15 };
 16 
 17 struct City
 18 {
 19     std::string name;
 20     std::vector<Route> routes;
 21     int happiness;
 22     int dist = std::numeric_limits<int>::max();
 23     std::vector<std::vector<int>> paths;
 24 };
 25 
 26 struct Collect
 27 {
 28     Collect(int dest, int dist)
 29         : dest(dest), dist(dist) { }
 30     int dest;
 31     int dist;
 32 };
 33 
 34 struct Comparator
 35 {
 36     static void init(std::vector<City>& _cities)
 37     {
 38         cities = &_cities;
 39     }
 40     static std::vector<City>* cities;
 41     bool operator()(const Collect& lhs, const Collect& rhs)
 42     {
 43         return (*cities)[lhs.dest].dist > (*cities)[rhs.dest].dist;
 44     }
 45 };
 46 
 47 std::vector<City>* Comparator::cities = nullptr;
 48 
 49 int main()
 50 {
 51     int num_cities, num_routes;
 52     std::cin >> num_cities >> num_routes;
 53     std::vector<City> cities(num_cities);
 54     std::cin >> cities[0].name;
 55     std::map<std::string, int> city_map;
 56     for (int i = 1; i != num_cities; ++i)
 57     {
 58         std::cin >> cities[i].name;
 59         std::cin >> cities[i].happiness;
 60         city_map[cities[i].name] = i;
 61     }
 62     for (int i = 0; i != num_routes; ++i)
 63     {
 64         std::string n0, n1;
 65         std::cin >> n0 >> n1;
 66         auto i0 = city_map[n0], i1 = city_map[n1];
 67         int dist;
 68         std::cin >> dist;
 69         cities[i0].routes.emplace_back(i1, dist);
 70         cities[i1].routes.emplace_back(i0, dist);
 71     }
 72     Comparator::init(cities);
 73     cities[0].dist = 0;
 74     cities[0].paths.resize(1);
 75     std::priority_queue<Collect, std::vector<Collect>, Comparator> queue;
 76     queue.emplace(0, 0);
 77     while (!queue.empty())
 78     {
 79         auto col = queue.top();
 80         queue.pop();
 81         auto& city = cities[col.dest];
 82         if (city.name == "ROM")
 83             break;
 84         if (city.dist != col.dist)
 85             continue;
 86         for (const auto& r : city.routes)
 87         {
 88             auto& target = cities[r.dest];
 89             if (city.dist + r.dist < target.dist)
 90             {
 91                 target.dist = city.dist + r.dist;
 92                 target.paths = city.paths;
 93                 for (auto& p : target.paths)
 94                     p.push_back(col.dest);
 95                 queue.emplace(r.dest, target.dist);
 96             }
 97             else if (city.dist + r.dist == target.dist)
 98             {
 99                 for (auto p : city.paths)
100                 {
101                     p.push_back(col.dest);
102                     target.paths.push_back(std::move(p));
103                 }
104             }
105         }
106     }
107     auto& rome = cities[city_map["ROM"]];
108     std::cout << rome.paths.size() << ' ' << rome.dist << ' ';
109     int happiness = 0;
110     int count = 0;
111     int recommended;
112     for (auto p = rome.paths.begin(); p != rome.paths.end(); ++p)
113     {
114         int h = 0;
115         for (int i : *p)
116             h += cities[i].happiness;
117         if (h > happiness)
118         {
119             happiness = h;
120             count = p->size();
121             recommended = p - rome.paths.begin();
122         }
123         else if (h == happiness && p->size() < count)
124         {
125             count = p->size();
126             recommended = p - rome.paths.begin();
127         }
128     }
129     happiness += rome.happiness;
130     std::cout << happiness << ' ' << happiness / (count ? count : 1) << std::endl;
131     for (int i : rome.paths[recommended])
132         std::cout << cities[i].name << "->";
133     std::cout << "ROM" << std::endl;
134 }

第一次提交的时候,一个case出现浮点错误。我没有用过浮点,那就是除以0的错误。这个case肯定是一种边界情况,但是我不怎么清楚路径长度是怎么为0的。也许是出发点就是终点吧。

 

1076:

3000ms真的吓到我了,啥题需要3000ms呢?怀着忐忑的心情我做完了这道题,跑出来140ms,而且我用的是C++的输入输出。因此我有理由相信这道题本应是300ms。

这里的图是没有权重的,因此就是一个简单的层序遍历,只是要维护层数并判断。把层数放入对象,像Dijkstra算法中维护距离一样,在处理一个对象时把后继节点的层数设为此对象的层数加1就可以了。

 1 #include <iostream>
 2 #include <vector>
 3 #include <queue>
 4 
 5 struct User
 6 {
 7     std::vector<int> fans;
 8     int layer;
 9     bool forward;
10 };
11 
12 int main()
13 {
14     int num_user, layer;
15     std::cin >> num_user >> layer;
16     std::vector<User> users(num_user);
17     for (int i = 0; i != num_user; ++i)
18     {
19         auto& u = users[i];
20         int count;
21         std::cin >> count;
22         for (int j = 0; j != count; ++j)
23         {
24             int t;
25             std::cin >> t;
26             --t;
27             users[t].fans.push_back(i);
28         }
29     }
30     int num_query;
31     std::cin >> num_query;
32     for (int i = 0; i != num_query; ++i)
33     {
34         int t;
35         std::cin >> t;
36         --t;
37         for (auto& u : users)
38             u.layer = 0, u.forward = false;
39         users[t].forward = true;
40         int count = 0;
41         std::queue<int> queue;
42         queue.push(t);
43         while (!queue.empty())
44         {
45             auto index = queue.front();
46             auto& user = users[index];
47             queue.pop();
48             for (int i : user.fans)
49             {
50                 auto& fan = users[i];
51                 if (!fan.forward)
52                 {
53                     //std::cout << i << ' ';
54                     fan.forward = true;
55                     ++count;
56                     fan.layer = user.layer + 1;
57                     if (fan.layer < layer)
58                         queue.push(i);
59                 }
60             }
61         }
62         std::cout << count << std::endl;
63     }
64 }

算法很正确,但是样例就错了。把转发的用户打印出来看,我发现最初发微博的用户作为自己的粉丝的粉丝……的粉丝,把自己的微博转发了一下。于是我就在初始条件里面把这个用户设置为已转发,但不计数。要是样例输入不挖这个坑,我可能要很久才能发现这个问题。

 

1091:

给定立体图像,计算每一块的体积并选择达到阈值的进行累加。

对于一个有效单位,可以用像层序遍历一样的算法把与之连通的单位都计算进来,同时将它们都标记为以收集。碰到下一个未收集的有效单位,再用同样的方法计算,直到遍历完。

实现中还用到了昨天提到的内部迭代器,用统一的接口包装输入和计算两个看似毫不相关的操作。

  1 #include <iostream>
  2 #include <cstdio>
  3 #include <queue>
  4 
  5 struct Unit
  6 {
  7     bool bad;
  8     bool collected = false;
  9 };
 10 
 11 struct Point
 12 {
 13     Point()
 14         : Point(0, 0, 0) { }
 15     Point(int x, int y, int z)
 16         : x(x), y(y), z(z) { }
 17     int x;
 18     int y;
 19     int z;
 20 };
 21 
 22 class Brain
 23 {
 24 public:
 25     Brain(Point p)
 26         : size(p)
 27     {
 28         data = new Unit[size.x * size.y * size.z];
 29     }
 30     ~Brain()
 31     {
 32         delete[] data;
 33     }
 34     Brain(const Brain&) = delete;
 35     Brain(Brain&&) = delete;
 36     Brain& operator=(const Brain&) = delete;
 37     Brain& operator=(Brain&&) = delete;
 38     Unit& operator[](const Point& p)
 39     {
 40         return data[point_to_int(p)];
 41     }
 42     template <typename F>
 43     void for_each(F _functor)
 44     {
 45         Point p;
 46         for (p.x = 0; p.x != size.x; ++p.x)
 47             for (p.y = 0; p.y != size.y; ++p.y)
 48                 for (p.z = 0; p.z != size.z; ++p.z)
 49                     _functor(p, operator[](p));
 50     }
 51     Point size;
 52 private:
 53     Unit* data;
 54     int point_to_int(const Point& p)
 55     {
 56         return (p.x * size.y + p.y) * size.z + p.z;
 57     }
 58 };
 59 
 60 void push_if_valid(std::queue<Point>& queue, Brain& brain, const Point& point)
 61 {
 62     if (point.x >= 0 && point.x < brain.size.x &&
 63         point.y >= 0 && point.y < brain.size.y &&
 64         point.z >= 0 && point.z < brain.size.z &&
 65         brain[point].bad &&
 66         !brain[point].collected)
 67         queue.emplace(point);
 68 }
 69 
 70 void push_if_valid(std::queue<Point>& queue, Brain& brain, int x, int y, int z)
 71 {
 72     if (x >= 0 && x < brain.size.x &&
 73         y >= 0 && y < brain.size.y &&
 74         z >= 0 && z < brain.size.z)
 75     {
 76         Point p(x, y, z);
 77         auto& unit = brain[p];
 78         if (unit.bad && !unit.collected)
 79             queue.emplace(std::move(p));
 80     }
 81         
 82 }
 83 
 84 int main()
 85 {
 86     int size_x, size_y, size_z, threshold;
 87     std::cin >> size_y >> size_z >> size_x >> threshold;
 88     Point size(size_x, size_y, size_z);
 89     Brain brain(size);
 90     brain.for_each([](const Point& p, Unit& u) {
 91         //std::cin >> u.bad;
 92         int t;
 93         std::scanf("%d", &t);
 94         u.bad = t;
 95     });
 96     int total = 0;
 97     brain.for_each([=, &brain, &total](const Point& p, Unit& u) {
 98         if (!u.bad || u.collected)
 99             return;
100         int size = 0;
101         std::queue<Point> queue;
102         queue.push(p);
103         while (!queue.empty())
104         {
105             auto p = queue.front();
106             queue.pop();
107             auto& unit = brain[p];
108             if (!unit.bad || unit.collected)
109                 continue;
110             unit.collected = true;
111             ++size;
112             push_if_valid(queue, brain, p.x - 1, p.y, p.z);
113             push_if_valid(queue, brain, p.x + 1, p.y, p.z);
114             push_if_valid(queue, brain, p.x, p.y - 1, p.z);
115             push_if_valid(queue, brain, p.x, p.y + 1, p.z);
116             push_if_valid(queue, brain, p.x, p.y, p.z - 1);
117             push_if_valid(queue, brain, p.x, p.y, p.z + 1);
118         }
119         if (size >= threshold)
120             total += size;
121     });
122     std::cout << total;
123 }

然而,这道题有个坑,在于输入数据第一行中3个维度的长度不是x、y、z的顺序,而是y、z、x的顺序。如果顺序不对,那么后面判断连通也会错误。如果按x、y、z来处理,样例数据的输出会是28而不是26,除了那大片区域外的4个点连通起来了。

 

posted on 2019-09-04 01:05  Jerry_SJTU  阅读(499)  评论(0编辑  收藏  举报