27.6 แบบฝึกหัด
เขียนแต่ละข้อเป็นไฟล์ใหม่ใน Code::Blocks รันแล้วเทียบกับผลลัพธ์ที่ต้องได้ให้ตรงทุกตัวอักษร รวมถึงช่องว่างและการขึ้นบรรทัด ติดจริงๆ ค่อยเปิดคำใบ้ และเปิดเฉลยหลังทำเสร็จแล้วเท่านั้น
ข้อ 1 ข่าวลือในหอพัก
หอพักนักเรียนมีนักเรียน N คน หมายเลข 1 ถึง N และมีเพื่อนสนิทกัน M คู่ เป็นเพื่อนกันทั้งสองทาง วันที่ 0 นักเรียนหมายเลข S ได้ยินข่าวลือเรื่องหนึ่ง ทุกวันหลังจากนั้น ทุกคนที่รู้ข่าวแล้วจะเล่าให้เพื่อนสนิททุกคนของตัวเองฟัง คนที่ได้ฟังในวันที่ d จะเริ่มเล่าต่อในวันที่ d + 1
ให้หาว่าสุดท้ายแล้วมีนักเรียนรู้ข่าวนี้กี่คน นับ S ด้วย และคนสุดท้ายที่รู้ข่าวได้ฟังในวันที่เท่าไร
ข้อมูลเข้า
บรรทัดแรกมีจำนวนเต็ม N M และ S คั่นด้วยช่องว่าง โดยที่ 1 ≤ N ≤ 200000, 0 ≤ M ≤ 200000 และ 1 ≤ S ≤ N
อีก M บรรทัด แต่ละบรรทัดมีจำนวนเต็ม a และ b แปลว่า a กับ b เป็นเพื่อนสนิทกัน โดยที่ a ≠ b และไม่มีคู่ไหนซ้ำ
ข้อมูลออก
บรรทัดเดียว มีจำนวนคนที่รู้ข่าว และวันที่คนสุดท้ายได้ฟัง คั่นด้วยช่องว่าง ถ้าไม่มีใครได้ฟังต่อจาก S เลย วันนั้นคือ 0
9 9 1
1 2
1 4
2 3
2 5
4 5
5 6
3 6
7 8
8 96 35 2 5
1 2
3 41 0ดูคำใบ้
วันที่แต่ละคนได้ฟังข่าวคือระยะทางจาก S ในกราฟที่ไม่มีน้ำหนัก BFS จาก S แบบหน้า 27.3 ให้คำตอบทั้งสองอย่างในรอบเดียว จำนวนคนที่ออกจาก queue คือจำนวนคนที่รู้ข่าว และระยะของคนสุดท้ายที่ออกจาก queue คือวันที่มากที่สุด การจำลองทีละวันแล้วไล่ทุกคู่เพื่อนทุกวันช้าเกินไปเมื่อข่าวต้องเดินทางเป็นแสนวัน ซึ่งเกิดได้ถ้าเพื่อนต่อกันเป็นสายยาว
ดูเฉลย
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 200001;
vector<int> adj[MAXN];
int heard_on[MAXN];
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m, s;
cin >> n >> m >> s;
for (int i = 0; i < m; i++) {
int a, b;
cin >> a >> b;
adj[a].push_back(b);
adj[b].push_back(a);
}
fill(heard_on, heard_on + n + 1, -1);
queue<int> waiting;
heard_on[s] = 0;
waiting.push(s);
int heard = 0, last_day = 0;
while (!waiting.empty()) {
int v = waiting.front();
waiting.pop();
heard++;
last_day = heard_on[v];
for (int next : adj[v]) {
if (heard_on[next] == -1) {
heard_on[next] = heard_on[v] + 1;
waiting.push(next);
}
}
}
cout << heard << ' ' << last_day << '\n';
return 0;
}ข้อ 2 เกาะที่อยู่ในภาพทั้งเกาะ
ภาพถ่ายดาวเทียมของหมู่เกาะแห่งหนึ่งแปลงเป็นแผนที่ R แถว C คอลัมน์ ช่อง # เป็นแผ่นดิน ช่อง . เป็นน้ำ แผ่นดินสองช่องอยู่บนเกาะเดียวกันถ้าเดินจากช่องหนึ่งไปอีกช่องได้ โดยก้าวทีละช่องไปทางบน ล่าง ซ้าย หรือขวา และเหยียบแต่แผ่นดิน ช่องที่แตะกันแค่มุมแบบทแยงไม่นับว่าติดกัน เกาะมีรูปร่างแบบไหนก็ได้ และอาจมีทะเลสาบอยู่ข้างใน
ภาพนี้ถ่ายทะเลมาแค่บางส่วน เกาะที่มีแผ่นดินแม้แต่ช่องเดียวอยู่ในแถวแรก แถวสุดท้าย คอลัมน์แรก หรือคอลัมน์สุดท้ายของแผนที่ อาจมีส่วนที่เลยขอบภาพออกไป จึงยังไม่รู้ขนาดจริง ให้นับเฉพาะเกาะที่ไม่แตะขอบแผนที่เลย และหาว่าเกาะที่ใหญ่ที่สุดในกลุ่มนี้มีแผ่นดินกี่ช่อง
ข้อมูลเข้า
บรรทัดแรกมีจำนวนเต็ม R และ C คั่นด้วยช่องว่าง โดยที่ 1 ≤ R, C ≤ 1000
อีก R บรรทัด บรรทัดละ C ตัวอักษรติดกัน แต่ละตัวเป็น # หรือ .
ข้อมูลออก
บรรทัดเดียว มีจำนวนเกาะที่ไม่แตะขอบแผนที่ และจำนวนช่องของเกาะที่ใหญ่ที่สุดในกลุ่มนั้น คั่นด้วยช่องว่าง ถ้าไม่มีเกาะแบบนี้เลยให้พิมพ์ 0 0
6 10
..........
.##...#...
.#..####..
....#...##
.##.....##
.##..#.###2 65 5
.....
.###.
.#.#.
.###.
.....1 8ดูคำใบ้
เริ่มจากการนับเกาะแบบหน้า 27.4 แล้วคิดว่าระหว่างเดินทั่วเกาะ จะรู้ได้อย่างไรว่าเกาะนั้นแตะขอบแผนที่ ระวังว่าช่องแรกที่เจอของเกาะอาจอยู่กลางแผนที่ ขณะที่ช่องอื่นของเกาะเดียวกันไปแตะแถวล่างสุดหรือคอลัมน์ขวาสุด และเกาะหนึ่งใหญ่ได้หลายแสนช่อง
ดูเฉลย
#include <bits/stdc++.h>
using namespace std;
int dr[4] = {-1, 1, 0, 0};
int dc[4] = {0, 0, -1, 1};
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int rows, cols;
cin >> rows >> cols;
vector<string> grid(rows);
for (int r = 0; r < rows; r++) {
cin >> grid[r];
}
vector<vector<bool>> seen(rows, vector<bool>(cols, false));
int islands = 0, largest = 0;
for (int r = 0; r < rows; r++) {
for (int c = 0; c < cols; c++) {
if (grid[r][c] != '#' || seen[r][c]) {
continue;
}
int cells = 0;
bool on_edge = false;
queue<pair<int, int>> waiting;
seen[r][c] = true;
waiting.push({r, c});
while (!waiting.empty()) {
auto [cr, cc] = waiting.front();
waiting.pop();
cells++;
if (cr == 0 || cr == rows - 1 || cc == 0 || cc == cols - 1) {
on_edge = true;
}
for (int d = 0; d < 4; d++) {
int nr = cr + dr[d];
int nc = cc + dc[d];
if (nr >= 0 && nr < rows && nc >= 0 && nc < cols && grid[nr][nc] == '#' && !seen[nr][nc]) {
seen[nr][nc] = true;
waiting.push({nr, nc});
}
}
}
if (!on_edge) {
islands++;
largest = max(largest, cells);
}
}
}
cout << islands << ' ' << largest << '\n';
return 0;
}ข้อ 3 เขตวงแหวน
อำเภอหนึ่งมีหมู่บ้าน N แห่ง หมายเลข 1 ถึง N และมีถนน M เส้น แต่ละเส้นเชื่อมสองหมู่บ้าน วิ่งได้ทั้งสองทาง ไม่มีถนนที่เชื่อมหมู่บ้านกับตัวเอง และไม่มีสองเส้นที่เชื่อมหมู่บ้านคู่เดียวกัน หมู่บ้านที่ขับรถไปหากันได้ไม่ว่าจะผ่านกี่เส้น อยู่ในเขตเดียวกัน หมู่บ้านที่ไม่มีถนนเลยเป็นเขตที่มีหมู่บ้านเดียว
อำเภอจะจัดรถรับส่งนักเรียนแยกตามเขต จึงอยากรู้รูปแบบถนนของแต่ละเขต เขตหนึ่งมีถนนวน ถ้าในเขตนั้นเริ่มขับจากหมู่บ้านหนึ่ง แล้ววนกลับมาที่เดิมได้โดยไม่ใช้ถนนเส้นไหนซ้ำ เขตหนึ่งเป็นวงแหวน ถ้าเริ่มขับจากหมู่บ้านหนึ่งในเขต ผ่านทุกหมู่บ้านในเขตหมู่บ้านละครั้ง ใช้ถนนทุกเส้นในเขตเส้นละครั้ง แล้วกลับมาที่เดิมได้ เขตที่เป็นวงแหวนจึงมีหมู่บ้านอย่างน้อย 3 แห่ง ให้นับว่ามีกี่เขตที่ไม่มีถนนวน และกี่เขตที่เป็นวงแหวน
ข้อมูลเข้า
บรรทัดแรกมีจำนวนเต็ม N และ M คั่นด้วยช่องว่าง โดยที่ 1 ≤ N ≤ 200000 และ 0 ≤ M ≤ 200000
อีก M บรรทัด แต่ละบรรทัดมีจำนวนเต็ม a และ b แปลว่ามีถนนเชื่อมหมู่บ้าน a กับ b
ข้อมูลออก
บรรทัดเดียว มีจำนวนเขตที่ไม่มีถนนวน และจำนวนเขตที่เป็นวงแหวน คั่นด้วยช่องว่าง
9 9
1 2
1 4
2 3
2 5
4 5
5 6
3 6
7 8
8 91 07 6
1 2
2 3
3 4
4 1
5 6
6 71 1ดูคำใบ้
ส่วนแรกคือการหาส่วนเชื่อมต่อและตรวจวงจรแบบหน้า 27.2 ส่วนที่สองต้องคิดเพิ่ม เขตที่มีวงจรไม่ได้เป็นวงแหวนทุกเขต ลองวาดเขตที่มีวงจรพอดีหนึ่งวงแต่ยังไม่เป็นวงแหวน แล้วดูว่าหมู่บ้านในวงแหวนต่างจากหมู่บ้านในเขตนั้นตรงไหน เขตหนึ่งอาจยาวเป็นเส้นตรงหรือวงแหวนสองแสนหมู่บ้าน
ดูเฉลย
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 200001;
vector<int> adj[MAXN];
bool seen[MAXN];
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m;
cin >> n >> m;
for (int i = 0; i < m; i++) {
int a, b;
cin >> a >> b;
adj[a].push_back(b);
adj[b].push_back(a);
}
int no_loop = 0, ring = 0;
for (int start = 1; start <= n; start++) {
if (seen[start]) {
continue;
}
long long villages = 0, road_ends = 0;
bool all_two = true;
stack<int> waiting;
seen[start] = true;
waiting.push(start);
while (!waiting.empty()) {
int v = waiting.top();
waiting.pop();
villages++;
road_ends += adj[v].size();
if (adj[v].size() != 2) {
all_two = false;
}
for (int next : adj[v]) {
if (!seen[next]) {
seen[next] = true;
waiting.push(next);
}
}
}
if (road_ends / 2 == villages - 1) {
no_loop++;
} else if (all_two) {
ring++;
}
}
cout << no_loop << ' ' << ring << '\n';
return 0;
}ข้อ 4 เขาวงกตข้าวโพด
เขาวงกตข้าวโพดในงานเกษตรแฟร์วาดเป็นแผนที่ R แถว C คอลัมน์ ช่อง # คือแนวต้นข้าวโพดที่เดินผ่านไม่ได้ ช่อง . คือทางเดิน ผู้เล่นเริ่มที่ช่อง S ต้องไปรับธงที่ช่อง P ก่อน แล้วจึงไปที่ทางออก E ก้าวได้ทีละช่องไปทางบน ล่าง ซ้าย หรือขวา ช่อง S P และ E เดินผ่านได้เหมือนทางเดิน และเดินผ่าน E ก่อนรับธงได้ แต่ยังไม่นับว่าออก
ให้หาจำนวนก้าวที่น้อยที่สุดตั้งแต่ออกจาก S จนถึง E โดยผ่าน P แล้ว
ข้อมูลเข้า
บรรทัดแรกมีจำนวนเต็ม R และ C คั่นด้วยช่องว่าง โดยที่ 1 ≤ R, C ≤ 1000
อีก R บรรทัด บรรทัดละ C ตัวอักษรติดกัน แต่ละตัวเป็น # . S P หรือ E มี S P และ E อย่างละหนึ่งช่องพอดี
ข้อมูลออก
จำนวนก้าวที่น้อยที่สุด ถ้าทำไม่ได้ให้พิมพ์ -1
6 8
S..#....
.#.#.##.
.#...#..
.####.#.
...E#.#P
.#......193 5
S.#P#
..###
...E.-1ดูคำใบ้
เส้นทางที่ดีที่สุดแบ่งเป็นสองขาที่ไม่เกี่ยวกัน ขาแรกจาก S ไป P ให้สั้นที่สุด ขาที่สองจาก P ไป E ให้สั้นที่สุด เขียน BFS บนตารางแบบหน้า 27.4 เป็นฟังก์ชันที่รับจุดเริ่มกับจุดหมาย แล้วเรียกสองครั้ง ถ้าขาไหนไปไม่ถึงคำตอบคือ -1 การจำลองทีละนาทีแบบบทที่ 7 ต้องสแกนทั้งแผนที่ทุกนาที และทางงูเลื้อยในแผนที่ 1000 × 1000 ยาวได้ถึงห้าแสนก้าว
ดูเฉลย
#include <bits/stdc++.h>
using namespace std;
int dr[4] = {-1, 1, 0, 0};
int dc[4] = {0, 0, -1, 1};
int rows, cols;
vector<string> grid;
int steps_between(char from, char to)
{
vector<vector<int>> dist(rows, vector<int>(cols, -1));
queue<pair<int, int>> waiting;
for (int r = 0; r < rows; r++) {
for (int c = 0; c < cols; c++) {
if (grid[r][c] == from) {
dist[r][c] = 0;
waiting.push({r, c});
}
}
}
while (!waiting.empty()) {
auto [r, c] = waiting.front();
waiting.pop();
if (grid[r][c] == to) {
return dist[r][c];
}
for (int d = 0; d < 4; d++) {
int nr = r + dr[d];
int nc = c + dc[d];
if (nr >= 0 && nr < rows && nc >= 0 && nc < cols && grid[nr][nc] != '#' && dist[nr][nc] == -1) {
dist[nr][nc] = dist[r][c] + 1;
waiting.push({nr, nc});
}
}
}
return -1;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> rows >> cols;
grid.resize(rows);
for (int r = 0; r < rows; r++) {
cin >> grid[r];
}
int first = steps_between('S', 'P');
int second = steps_between('P', 'E');
if (first == -1 || second == -1) {
cout << -1 << '\n';
} else {
cout << first + second << '\n';
}
return 0;
}ข้อ 5 ถนนเดินรถทางเดียว
เมืองท่องเที่ยวแห่งหนึ่งมีจุดจอดรถ N จุด หมายเลข 1 ถึง N และมีถนนเดินรถทางเดียว M เส้น เส้นที่ i วิ่งได้จากจุด ai ไปจุด bi ทางเดียว รถรางนำเที่ยวออกจากจุด 1 เสมอ ฝ่ายวางแผนอยากรู้สามอย่างเกี่ยวกับจุดอื่นที่ไม่ใช่จุด 1
- มีกี่จุดที่รถรางขับจากจุด 1 ไปถึงได้
- มีกี่จุดที่ขับกลับมาถึงจุด 1 ได้
- มีกี่จุดที่ทั้งไปถึงได้และกลับมาจุด 1 ได้
ข้อมูลเข้า
บรรทัดแรกมีจำนวนเต็ม N และ M คั่นด้วยช่องว่าง โดยที่ 1 ≤ N ≤ 200000 และ 0 ≤ M ≤ 300000
อีก M บรรทัด บรรทัดที่ i มีจำนวนเต็ม ai และ bi โดยที่ ai ≠ bi และไม่มีถนนที่ซ้ำกันทั้งต้นทางและปลายทาง
ข้อมูลออก
บรรทัดเดียว มีจำนวนเต็มสามตัวตามลำดับข้างบน คั่นด้วยช่องว่าง ทั้งสามตัวไม่นับจุด 1
6 7
1 2
2 3
3 1
3 4
4 5
6 1
5 44 3 23 00 0 0ดูคำใบ้
ข้อแรกคือ BFS จากจุด 1 ตามทิศของถนนแบบกราฟมีทิศในหน้า 27.1 ข้อที่สองถ้าลอง BFS จากทุกจุดว่าถึงจุด 1 ไหม จะเป็น O(N(N + M)) ให้กลับคำถามแทน สร้างกราฟอีกชุดที่กลับทิศถนนทุกเส้น จุด v ขับไปถึงจุด 1 ได้ในกราฟเดิม ก็ต่อเมื่อจุด 1 ไปถึง v ได้ในกราฟที่กลับทิศ จึงใช้ BFS อีกครั้งเดียว ข้อที่สามคือจุดที่ผ่านทั้งสองรอบ
ดูเฉลย
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 200001;
vector<int> out_roads[MAXN];
vector<int> in_roads[MAXN];
// รับอาเรย์ของ vector แบบเดียวกับส่งอาเรย์เข้าฟังก์ชันในหน้า 8.3 ฟังก์ชันใช้ตัวจริง ไม่ได้คัดลอก
vector<bool> reachable(int n, vector<int> roads[])
{
vector<bool> seen(n + 1, false);
queue<int> waiting;
seen[1] = true;
waiting.push(1);
while (!waiting.empty()) {
int v = waiting.front();
waiting.pop();
for (int next : roads[v]) {
if (!seen[next]) {
seen[next] = true;
waiting.push(next);
}
}
}
return seen;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m;
cin >> n >> m;
for (int i = 0; i < m; i++) {
int from, to;
cin >> from >> to;
out_roads[from].push_back(to);
in_roads[to].push_back(from);
}
vector<bool> go = reachable(n, out_roads);
vector<bool> back = reachable(n, in_roads);
int go_count = 0, back_count = 0, both = 0;
for (int v = 2; v <= n; v++) {
go_count += go[v]; // true บวกเป็น 1 false บวกเป็น 0
back_count += back[v];
both += go[v] && back[v];
}
cout << go_count << ' ' << back_count << ' ' << both << '\n';
return 0;
}ข้อ 6 แบ่งทีมกีฬาสี
โรงเรียนจะแบ่งนักเรียน N คน หมายเลข 1 ถึง N เป็นสองสีสำหรับงานกีฬาสี คือสีแดงกับสีน้ำเงิน ทุกคนต้องอยู่สีใดสีหนึ่งพอดี ครูมีรายชื่อคู่ที่ห้ามอยู่สีเดียวกัน M คู่ เพราะเป็นคู่แข่งกันในรายการเดียวกัน นอกจากนี้ยังมีนักกีฬาตัวแทน K คนที่ลงทะเบียนสีไว้กับเขตการศึกษาแล้ว เปลี่ยนสีไม่ได้
ชุดกีฬาสีแดงเหลือน้อย ครูจึงอยากให้สีแดงมีคนน้อยที่สุดเท่าที่ทำได้ โดยยังไม่ผิดเงื่อนไขของคู่ใดเลย และนักกีฬาตัวแทนทุกคนได้สีที่ลงทะเบียนไว้ ให้หาว่าสีแดงมีคนน้อยที่สุดได้กี่คน หรือบอกว่าแบ่งไม่ได้เลย
ข้อมูลเข้า
บรรทัดแรกมีจำนวนเต็ม N M และ K คั่นด้วยช่องว่าง โดยที่ 1 ≤ N ≤ 200000, 0 ≤ M ≤ 200000 และ 0 ≤ K ≤ N
อีก M บรรทัด แต่ละบรรทัดมีจำนวนเต็ม a และ b แปลว่า a กับ b ห้ามอยู่สีเดียวกัน โดยที่ a ≠ b และไม่มีคู่ไหนซ้ำ
อีก K บรรทัด แต่ละบรรทัดมีหมายเลขนักกีฬาตัวแทน ตามด้วยตัวอักษร R ถ้าลงทะเบียนสีแดง หรือ B ถ้าลงทะเบียนสีน้ำเงิน คั่นด้วยช่องว่าง ไม่มีใครลงทะเบียนซ้ำ
ข้อมูลออก
จำนวนคนที่น้อยที่สุดในสีแดง ถ้าแบ่งตามเงื่อนไขไม่ได้ให้พิมพ์ -1
9 9 2
1 2
1 4
2 3
2 5
4 5
5 6
3 6
7 8
8 9
9 R
2 B53 2 2
1 2
2 3
1 R
3 B-1ดูคำใบ้
ถ้าไม่มีนักกีฬาตัวแทน นี่คือการตรวจกราฟสองฝั่งในหน้า 27.3 แล้วคิดเพิ่มว่าในแต่ละกลุ่มที่เชื่อมกัน สีถูกบังคับไปมากแค่ไหน และเหลืออะไรให้เลือก จากนั้นดูว่านักกีฬาตัวแทนหนึ่งคนตัดทางเลือกของกลุ่มเขาไปอย่างไร ระวังกลุ่มที่มีนักกีฬาตัวแทนหลายคน
ดูเฉลย
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 200001;
vector<int> adj[MAXN];
int team[MAXN];
char registered[MAXN];
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m, k;
cin >> n >> m >> k;
for (int i = 0; i < m; i++) {
int a, b;
cin >> a >> b;
adj[a].push_back(b);
adj[b].push_back(a);
}
for (int i = 0; i < k; i++) {
int v;
char color;
cin >> v >> color;
registered[v] = color;
}
fill(team, team + n + 1, -1);
int red = 0;
for (int start = 1; start <= n; start++) {
if (team[start] != -1) {
continue;
}
int side_size[2] = {0, 0};
int red_side = -1;
queue<int> waiting;
team[start] = 0;
waiting.push(start);
while (!waiting.empty()) {
int v = waiting.front();
waiting.pop();
side_size[team[v]]++;
if (registered[v] != 0) {
int need = (registered[v] == 'R' ? team[v] : 1 - team[v]);
if (red_side == -1) {
red_side = need;
} else if (red_side != need) {
cout << -1 << '\n';
return 0;
}
}
for (int next : adj[v]) {
if (team[next] == -1) {
team[next] = 1 - team[v];
waiting.push(next);
} else if (team[next] == team[v]) {
cout << -1 << '\n';
return 0;
}
}
}
if (red_side == -1) {
red += min(side_size[0], side_size[1]);
} else {
red += side_size[red_side];
}
}
cout << red << '\n';
return 0;
}ข้อ 7 สาขาที่ใกล้ที่สุด
ร้านชานมแห่งหนึ่งมีสาขาอยู่ในหมู่บ้าน K แห่ง จากหมู่บ้านทั้งหมด N แห่ง หมายเลข 1 ถึง N หมู่บ้านเชื่อมกันด้วยถนน M เส้น วิ่งได้ทั้งสองทาง และวิ่งถนนแต่ละเส้นใช้เวลาเท่ากัน ร้านจะให้แต่ละหมู่บ้านสั่งจากสาขาที่ต้องวิ่งผ่านถนนน้อยเส้นที่สุด ถ้ามีหลายสาขาที่ใกล้เท่ากัน ให้สาขาที่ตั้งอยู่ในหมู่บ้านหมายเลขน้อยที่สุดเป็นคนรับ หมู่บ้านที่มีสาขาอยู่แล้วสั่งจากสาขาของตัวเอง ส่วนหมู่บ้านที่ไปไม่ถึงสาขาไหนเลยไม่ได้สั่ง
ให้หาว่าแต่ละสาขาต้องรับออร์เดอร์จากกี่หมู่บ้าน นับหมู่บ้านของตัวเองด้วย
ข้อมูลเข้า
บรรทัดแรกมีจำนวนเต็ม N M และ K คั่นด้วยช่องว่าง โดยที่ 1 ≤ K ≤ N ≤ 200000 และ 0 ≤ M ≤ 300000
บรรทัดที่สองมีจำนวนเต็ม K ตัวไม่ซ้ำกัน คือหมู่บ้านที่มีสาขา ไม่ได้เรียงมา
อีก M บรรทัด แต่ละบรรทัดมีจำนวนเต็ม a และ b แปลว่ามีถนนเชื่อมหมู่บ้าน a กับ b โดยที่ a ≠ b และไม่มีคู่ไหนซ้ำ
ข้อมูลออก
บรรทัดเดียว มีจำนวนเต็ม K ตัว ตัวที่ i คือจำนวนหมู่บ้านที่สาขาตัวที่ i ในบรรทัดที่สองของข้อมูลเข้าต้องรับ คั่นด้วยช่องว่าง
5 4 2
5 1
1 2
2 3
3 4
4 52 39 9 2
9 4
1 2
1 4
2 3
2 5
4 5
5 6
3 6
7 8
8 93 6ดูคำใบ้
ใช้ BFS หลายจุดเริ่มในหน้า 27.3 ใส่ทุกสาขาลง queue ตั้งแต่แรกด้วยระยะ 0 และเก็บ owner ว่าหมู่บ้านนี้เป็นของสาขาไหน ตอนเจอหมู่บ้านครั้งแรกให้รับ owner ของหมู่บ้านที่พามาถึง ส่วนตอนเจอหมู่บ้านที่ระยะเท่ากับ dist[v] + 1 อยู่แล้ว แปลว่ามีอีกสาขาที่ใกล้เท่ากัน ให้เก็บ owner ที่น้อยกว่าไว้ วิธีนี้ถูกเพราะทุกหมู่บ้านที่ระยะ d ออกจาก queue ก่อนหมู่บ้านที่ระยะ d + 1 เสมอ การทำ BFS แยกจากทุกสาขาถูกเหมือนกัน แต่เป็น O(K(N + M)) ไม่ทันเมื่อมีสาขาเป็นหมื่น
ดูเฉลย
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 200001;
vector<int> adj[MAXN];
int dist[MAXN];
int owner[MAXN];
int served[MAXN];
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m, k;
cin >> n >> m >> k;
vector<int> branches(k);
for (int i = 0; i < k; i++) {
cin >> branches[i];
}
for (int i = 0; i < m; i++) {
int a, b;
cin >> a >> b;
adj[a].push_back(b);
adj[b].push_back(a);
}
fill(dist, dist + n + 1, -1);
queue<int> waiting;
for (int b : branches) {
dist[b] = 0;
owner[b] = b;
waiting.push(b);
}
while (!waiting.empty()) {
int v = waiting.front();
waiting.pop();
served[owner[v]]++;
for (int next : adj[v]) {
if (dist[next] == -1) {
dist[next] = dist[v] + 1;
owner[next] = owner[v];
waiting.push(next);
} else if (dist[next] == dist[v] + 1 && owner[v] < owner[next]) {
owner[next] = owner[v];
}
}
}
for (int i = 0; i < k; i++) {
cout << served[branches[i]] << (i + 1 == k ? '\n' : ' ');
}
return 0;
}ข้อ 8 เส้นทางรถไฟฟ้า
ระบบรถไฟฟ้าของเมืองหนึ่งมีสถานี N สถานี หมายเลข 1 ถึง N และมีช่วงราง M ช่วง แต่ละช่วงเชื่อมสองสถานี วิ่งได้ทั้งสองทาง แอปนำทางต้องแนะนำเส้นทางจากสถานี s ไปสถานี t ที่ผ่านช่วงรางน้อยที่สุด เส้นทางสั้นสุดมักมีหลายเส้น แอปจึงเลือกเส้นที่น้อยที่สุดตามพจนานุกรม คือเทียบรายชื่อสถานีที่ผ่านทีละตัวจากต้นทาง ตัวแรกที่ต่างกัน เส้นที่สถานีตรงนั้นมีหมายเลขน้อยกว่าเป็นเส้นที่เลือก
เช่น ถ้าเส้นทางสั้นสุดมี 4 1 2 3 4 5 2 3 และ 4 5 6 3 แอปจะเลือก 4 1 2 3 เพราะสถานีที่สองคือ 1 ซึ่งน้อยกว่า 5
ข้อมูลเข้า
บรรทัดแรกมีจำนวนเต็ม N M s และ t คั่นด้วยช่องว่าง โดยที่ 1 ≤ N ≤ 200000, 0 ≤ M ≤ 400000 และ 1 ≤ s, t ≤ N
อีก M บรรทัด แต่ละบรรทัดมีจำนวนเต็ม a และ b แปลว่ามีช่วงรางระหว่างสถานี a กับ b โดยที่ a ≠ b และไม่มีคู่ไหนซ้ำ
ข้อมูลออก
ถ้าไปจาก s ถึง t ไม่ได้ ให้พิมพ์ -1 บรรทัดเดียว
ถ้าไปได้ บรรทัดแรกพิมพ์จำนวนช่วงรางที่น้อยที่สุด L บรรทัดที่สองพิมพ์หมายเลขสถานี L + 1 ตัวของเส้นทางที่แอปเลือก ตั้งแต่ s ถึง t คั่นด้วยช่องว่าง
9 9 4 3
1 2
1 4
2 3
2 5
4 5
5 6
3 6
7 8
8 93
4 1 2 39 9 1 9
1 2
1 4
2 3
2 5
4 5
5 6
3 6
7 8
8 9-1ดูคำใบ้
เก็บพ่อตอน BFS จาก s แบบหน้า 27.3 ได้เส้นทางสั้นสุดเส้นหนึ่ง แต่เส้นไหนขึ้นกับลำดับในรายการเพื่อนบ้าน ถ้า sort ทุกรายการก่อน BFS จะได้เส้นที่น้อยที่สุดตามพจนานุกรมพอดี เพราะจุดในชั้นเดียวกันออกจาก queue เรียงตามเส้นทางของพ่อ แล้วตามหมายเลขของตัวเอง อีกวิธีที่เห็นเหตุผลง่ายกว่าคือ BFS จาก t ได้ to_goal[v] คือระยะจาก v ถึง t แล้วเดินจาก s ทีละก้าว เลือกเพื่อนบ้านหมายเลขน้อยที่สุดที่ to_goal น้อยกว่าสถานีปัจจุบันหนึ่งพอดี เส้นทางทุกเส้นยาวเท่ากัน ตัวแรกที่ต่างกันจึงตัดสินผล ทั้งสองวิธีเป็น O(N + M) ไม่รวมเวลา sort ส่วนการ BFS ใหม่เพื่อตรวจทุกตัวเลือกทุกก้าวไม่ทันเมื่อเส้นทางยาวเป็นแสนช่วง
ดูเฉลย
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 200001;
vector<int> adj[MAXN];
int to_goal[MAXN];
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m, start, goal;
cin >> n >> m >> start >> goal;
for (int i = 0; i < m; i++) {
int a, b;
cin >> a >> b;
adj[a].push_back(b);
adj[b].push_back(a);
}
fill(to_goal, to_goal + n + 1, -1);
queue<int> waiting;
to_goal[goal] = 0;
waiting.push(goal);
while (!waiting.empty()) {
int v = waiting.front();
waiting.pop();
for (int next : adj[v]) {
if (to_goal[next] == -1) {
to_goal[next] = to_goal[v] + 1;
waiting.push(next);
}
}
}
if (to_goal[start] == -1) {
cout << -1 << '\n';
return 0;
}
cout << to_goal[start] << '\n';
int v = start;
cout << v;
while (v != goal) {
int best = -1;
for (int next : adj[v]) {
if (to_goal[next] == to_goal[v] - 1 && (best == -1 || next < best)) {
best = next;
}
}
v = best;
cout << ' ' << v;
}
cout << '\n';
return 0;
}ข้อ 9 ปริศนาป้ายเลื่อน
ของเล่นปริศนาเป็นกรอบ 3 × 3 มีป้ายตัวเลข 1 ถึง 8 อย่างละหนึ่งป้าย และช่องว่างหนึ่งช่อง การเล่นหนึ่งครั้งคือเลื่อนป้ายที่อยู่ติดกับช่องว่างทางบน ล่าง ซ้าย หรือขวา เข้าไปในช่องว่าง เป้าหมายคือจัดให้เป็น
1 2 3
4 5 6
7 8 _
ร้านของเล่นได้รับของเล่นที่ลูกค้าเล่นค้างไว้ T ชิ้น ให้หาว่าแต่ละชิ้นต้องเลื่อนอย่างน้อยกี่ครั้งจึงถึงเป้าหมาย บางชิ้นอาจถูกแกะป้ายออกมาใส่ผิดตำแหน่ง ซึ่งเลื่อนยังไงก็ไม่มีทางถึงเป้าหมาย
ข้อมูลเข้า
บรรทัดแรกมีจำนวนเต็ม T โดยที่ 1 ≤ T ≤ 100000
อีก T บรรทัด บรรทัดละหนึ่งชิ้น เป็นตัวเลข 9 ตัวติดกัน อ่านกรอบจากแถวบนลงแถวล่าง แต่ละแถวจากซ้ายไปขวา ใช้ 0 แทนช่องว่าง เลข 0 ถึง 8 ปรากฏอย่างละหนึ่งครั้งพอดี เป้าหมายจึงเขียนว่า 123456780
ข้อมูลออก
T บรรทัด บรรทัดที่ i คือจำนวนครั้งที่น้อยที่สุดของชิ้นที่ i ถ้าไปไม่ถึงเป้าหมายให้พิมพ์ -1
4
123456780
123450786
120453786
2134567800
1
2
-12
123406758
0124537862
4ดูคำใบ้
สถานะคือสตริงยาว 9 ตัว มีได้ไม่เกิน 9! = 362880 แบบ จำระยะด้วย map<string, int> แบบหน้า 27.5 การเลื่อนย้อนกลับได้เสมอ จำนวนครั้งจากกระดานใดไปเป้าหมายจึงเท่ากับจากเป้าหมายมากระดานนั้น ให้ BFS ครั้งเดียวจากเป้าหมาย ตั้งแต่ก่อนอ่านคำถาม แล้วแต่ละคำถามแค่ค้นใน map กระดานที่ไม่อยู่ใน map คือไปไม่ถึง ถ้า BFS ใหม่จากทุกกระดาน กระดานที่ไปไม่ถึงต้องเดินครบเกือบสองแสนสถานะก่อนจะรู้ว่าไม่ถึง
ดูเฉลย
#include <bits/stdc++.h>
using namespace std;
int dr[4] = {-1, 1, 0, 0};
int dc[4] = {0, 0, -1, 1};
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
map<string, int> moves;
queue<string> waiting;
moves["123456780"] = 0;
waiting.push("123456780");
while (!waiting.empty()) {
string board = waiting.front();
waiting.pop();
size_t blank = board.find('0');
int r = blank / 3, c = blank % 3;
for (int d = 0; d < 4; d++) {
int nr = r + dr[d];
int nc = c + dc[d];
if (nr < 0 || nr >= 3 || nc < 0 || nc >= 3) {
continue;
}
string next = board;
swap(next[blank], next[nr * 3 + nc]);
if (moves.count(next) == 0) {
moves[next] = moves[board] + 1;
waiting.push(next);
}
}
}
int t;
cin >> t;
for (int i = 0; i < t; i++) {
string board;
cin >> board;
auto it = moves.find(board);
cout << (it == moves.end() ? -1 : it->second) << '\n';
}
return 0;
}ข้อ 10 ต้นไม้ล้มขวางทาง
หลังพายุ ทางเดินในสวนป่าแห่งหนึ่งมีต้นไม้ล้มขวางอยู่หลายจุด แผนที่มี R แถว C คอลัมน์ ช่อง . เดินได้ ช่อง T มีต้นไม้ล้ม ต้องตัดก่อนจึงเข้าไปได้ ช่อง # เป็นหินใหญ่ที่เข้าไปไม่ได้เลย เจ้าหน้าที่อยู่ที่ช่อง S ต้องไปที่ช่อง E ก้าวได้ทีละช่องไปทางบน ล่าง ซ้าย หรือขวา ช่อง S กับ E เดินได้เหมือนทางเดิน
เวลาเดินไม่สำคัญ แต่การตัดต้นไม้เหนื่อยมาก ให้หาจำนวนต้นไม้ที่ต้องตัดน้อยที่สุด
ข้อมูลเข้า
บรรทัดแรกมีจำนวนเต็ม R และ C คั่นด้วยช่องว่าง โดยที่ 1 ≤ R, C ≤ 1000
อีก R บรรทัด บรรทัดละ C ตัวอักษรติดกัน แต่ละตัวเป็น . T # S หรือ E มี S และ E อย่างละหนึ่งช่องพอดี
ข้อมูลออก
จำนวนต้นไม้ที่ต้องตัดน้อยที่สุด ถ้าไปไม่ถึงให้พิมพ์ -1
5 7
S.T....
##T.##.
..T.#T.
.##.#TT
...T#.E23 4
S.#.
TT#E
..#.-1ดูคำใบ้
ก้าวเข้าช่อง . หรือ E มีต้นทุน 0 ก้าวเข้าช่อง T มีต้นทุน 1 นี่คือ 0-1 BFS ในหน้า 27.5 บนตาราง ใช้ deque ก้าวต้นทุน 0 ใส่ด้านหน้า ก้าวต้นทุน 1 ใส่ด้านหลัง และอัปเดตเมื่อได้ค่าที่น้อยกว่าเดิมเท่านั้น BFS ธรรมดาที่จองช่องตั้งแต่เจอครั้งแรกให้คำตอบผิด เพราะช่องที่เจอก่อนอาจเจอผ่านต้นไม้ ส่วนการไล่ทีละจำนวนต้นแล้วสแกนทั้งแผนที่ทุกรอบช้าเกินไปเมื่อต้องตัดเป็นแสนต้น
ดูเฉลย
#include <bits/stdc++.h>
using namespace std;
int dr[4] = {-1, 1, 0, 0};
int dc[4] = {0, 0, -1, 1};
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int rows, cols;
cin >> rows >> cols;
vector<string> grid(rows);
for (int r = 0; r < rows; r++) {
cin >> grid[r];
}
vector<vector<int>> cuts(rows, vector<int>(cols, INT_MAX));
deque<pair<int, int>> waiting;
int goal_r = 0, goal_c = 0;
for (int r = 0; r < rows; r++) {
for (int c = 0; c < cols; c++) {
if (grid[r][c] == 'S') {
cuts[r][c] = 0;
waiting.push_back({r, c});
} else if (grid[r][c] == 'E') {
goal_r = r;
goal_c = c;
}
}
}
while (!waiting.empty()) {
auto [r, c] = waiting.front();
waiting.pop_front();
for (int d = 0; d < 4; d++) {
int nr = r + dr[d];
int nc = c + dc[d];
if (nr < 0 || nr >= rows || nc < 0 || nc >= cols || grid[nr][nc] == '#') {
continue;
}
int cost = (grid[nr][nc] == 'T' ? 1 : 0);
if (cuts[r][c] + cost < cuts[nr][nc]) {
cuts[nr][nc] = cuts[r][c] + cost;
if (cost == 0) {
waiting.push_front({nr, nc});
} else {
waiting.push_back({nr, nc});
}
}
}
}
int answer = cuts[goal_r][goal_c];
cout << (answer == INT_MAX ? -1 : answer) << '\n';
return 0;
}