17.5 แบบฝึกหัด
เขียนแต่ละข้อเป็นไฟล์ใหม่ใน Code::Blocks รันแล้วเทียบกับผลลัพธ์ที่ต้องได้ให้ตรงทุกตัวอักษร รวมถึงช่องว่างและการขึ้นบรรทัด ติดจริงๆ ค่อยเปิดคำใบ้ และเปิดเฉลยหลังทำเสร็จแล้วเท่านั้น
ข้อ 1 ตะกร้าคืนหนังสือ
ห้องสมุดมีตะกร้าคืนหนังสือ นักเรียนที่มาคืนหนังสือวางเล่มของตัวเองไว้บนสุดของกองในตะกร้า บรรณารักษ์จะหยิบหนังสือจากบนสุดไปเก็บขึ้นชั้นทีละหลายเล่ม ให้จำลองตะกร้าตามคำสั่ง Q คำสั่ง แต่ละคำสั่งเป็นแบบใดแบบหนึ่งต่อไปนี้ ตอนเริ่มตะกร้าว่าง
RETURN tนักเรียนวางหนังสือชื่อ t ไว้บนสุดของกองSHELVE kบรรณารักษ์หยิบหนังสือจากบนสุดออกไปทีละเล่มจนครบ k เล่ม ถ้าในตะกร้ามีไม่ถึง k เล่มให้หยิบจนหมด ให้พิมพ์ชื่อหนังสือที่หยิบตามลำดับที่หยิบในบรรทัดเดียว คั่นด้วยช่องว่างหนึ่งช่อง ถ้าตะกร้าว่างอยู่แล้วให้พิมพ์EMPTYTOPพิมพ์ชื่อหนังสือที่อยู่บนสุดโดยไม่หยิบออก ถ้าตะกร้าว่างให้พิมพ์EMPTY
ทำครบทุกคำสั่งแล้ว ให้พิมพ์จำนวนหนังสือที่ยังเหลือในตะกร้า
ข้อนี้ตั้งใจให้ฝึกย้ายวิธีจากข้อกองจานในบทที่ 14 มาใช้ stack ของ STL ที่ต่างคือของในกองเป็นข้อความ และหยิบทีละหลายเล่มในคำสั่งเดียว
ข้อมูลเข้า
บรรทัดแรกมีจำนวนเต็ม Q โดยที่ 1 ≤ Q ≤ 100000
อีก Q บรรทัด บรรทัดละหนึ่งคำสั่งตามรูปแบบข้างบน ชื่อหนังสือ t ยาว 1 ถึง 20 ตัวอักษร ประกอบด้วยตัวอักษรภาษาอังกฤษตัวเล็ก ตัวเลข และขีด - ไม่มีช่องว่าง ชื่อซ้ำกันได้ และ 1 ≤ k ≤ 100000
ข้อมูลออก
สำหรับคำสั่ง SHELVE และ TOP แต่ละคำสั่ง ให้พิมพ์คำตอบหนึ่งบรรทัดตามลำดับของคำสั่ง บรรทัดสุดท้ายพิมพ์จำนวนหนังสือที่เหลือในตะกร้า
10
RETURN math-1
RETURN thai-novel
TOP
RETURN physics
SHELVE 2
RETURN atlas
SHELVE 5
TOP
SHELVE 1
RETURN comicthai-novel
physics thai-novel
atlas math-1
EMPTY
EMPTY
1ดูคำใบ้
ใช้ stack<string> ตามหน้า 17.1 คำสั่ง SHELVE k ให้วนหยิบทีละเล่มโดยเช็กสองเงื่อนไขพร้อมกัน คือยังหยิบไม่ครบ k เล่ม และตะกร้ายังไม่ว่าง อย่าวนครบ k รอบโดยไม่ดูว่าหมดหรือยัง เพราะ top() ตอนว่างทำให้โปรแกรมพัง และ pop() ไม่คืนค่า ต้องพิมพ์ top() ก่อนแล้วค่อย pop() ระวังช่องว่างเกินท้ายบรรทัดด้วย
ดูเฉลย
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int q;
cin >> q;
stack<string> basket;
for (int i = 0; i < q; i++) {
string command;
cin >> command;
if (command == "RETURN") {
string title;
cin >> title;
basket.push(title);
} else if (command == "SHELVE") {
int k;
cin >> k;
if (basket.empty()) {
cout << "EMPTY\n";
} else {
for (int j = 0; j < k && !basket.empty(); j++) {
if (j > 0) {
cout << ' ';
}
cout << basket.top();
basket.pop();
}
cout << '\n';
}
} else {
if (basket.empty()) {
cout << "EMPTY\n";
} else {
cout << basket.top() << '\n';
}
}
}
cout << basket.size() << '\n';
return 0;
}ข้อ 2 ช่องขายตั๋ว
ช่องขายตั๋วคอนเสิร์ตเปิดขายพร้อมตั๋ว S ใบ แฟนเพลงต่อแถวเดียว แต่ละคนบอกตอนเข้าแถวว่าอยากซื้อกี่ใบ พอถึงคิว ถ้าตั๋วเหลือพอเขาจะได้ครบตามที่ขอ ถ้าเหลือไม่พอเขาจะได้เท่าที่เหลือ ซึ่งอาจเป็น 0 ใบก็ได้ แล้วออกจากแถวไป ให้จำลองช่องขายตั๋วตามคำสั่ง Q คำสั่ง แต่ละคำสั่งเป็นแบบใดแบบหนึ่งต่อไปนี้ ตอนเริ่มไม่มีใครในแถว
JOIN name tคนชื่อ name มาต่อท้ายแถว อยากซื้อ t ใบSELLคนหัวแถวซื้อตั๋วแล้วออกจากแถว ให้พิมพ์ชื่อของเขา ช่องว่างหนึ่งช่อง และจำนวนตั๋วที่เขาได้ ถ้าไม่มีใครในแถวให้พิมพ์EMPTYLASTพิมพ์ชื่อของคนท้ายแถว ถ้าไม่มีใครในแถวให้พิมพ์EMPTYCOUNTพิมพ์จำนวนคนที่ยังอยู่ในแถว ช่องว่างหนึ่งช่อง และจำนวนตั๋วที่ยังเหลือ
ข้อนี้ตั้งใจให้ฝึกย้ายวิธีจากข้อแถวรถไฟเหาะในบทที่ 14 มาใช้ queue ของ STL ที่ต่างคือแต่ละคนในแถวมีสองค่า และจำนวนตั๋วใหญ่เกิน int
ข้อมูลเข้า
บรรทัดแรกมีจำนวนเต็ม S และ Q คั่นด้วยช่องว่าง โดยที่ 0 ≤ S ≤ 1015 และ 1 ≤ Q ≤ 100000
อีก Q บรรทัด บรรทัดละหนึ่งคำสั่งตามรูปแบบข้างบน ชื่อ name ยาว 1 ถึง 15 ตัวอักษรภาษาอังกฤษ ไม่มีช่องว่าง และอาจซ้ำกันได้ จำนวนตั๋วที่ขอ t เป็นจำนวนเต็ม 1 ≤ t ≤ 1000000000
ข้อมูลออก
สำหรับคำสั่ง SELL LAST และ COUNT แต่ละคำสั่ง ให้พิมพ์คำตอบหนึ่งบรรทัดตามลำดับของคำสั่ง
10 9
JOIN Ploy 4
JOIN Krit 3
JOIN Mint 5
LAST
SELL
SELL
COUNT
SELL
SELLMint
Ploy 4
Krit 3
1 3
Mint 3
EMPTYดูคำใบ้
ใช้ queue<pair<string, int>> เก็บชื่อกับจำนวนที่ขอ แบบโปรแกรม copy-shop ในหน้า 17.1 ดูท้ายแถวด้วย back() และดูหัวแถวด้วย front() ก่อน pop() ตั๋ว S ใหญ่ได้ถึง 1015 เกิน int ไปมาก ต้องเป็น long long และเวลาหาค่าน้อยกว่าระหว่างจำนวนที่ขอกับตั๋วที่เหลือด้วย min ทั้งสองค่าต้องเป็นชนิดเดียวกัน คนที่มาตอนตั๋วหมดแล้วยังต้องพิมพ์ชื่อตามด้วย 0
ดูเฉลย
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
long long tickets;
int q;
cin >> tickets >> q;
queue<pair<string, int>> line;
for (int i = 0; i < q; i++) {
string command;
cin >> command;
if (command == "JOIN") {
string name;
int want;
cin >> name >> want;
line.push({name, want});
} else if (command == "SELL") {
if (line.empty()) {
cout << "EMPTY\n";
} else {
auto fan = line.front();
line.pop();
long long got = min((long long)fan.second, tickets);
tickets -= got;
cout << fan.first << ' ' << got << '\n';
}
} else if (command == "LAST") {
if (line.empty()) {
cout << "EMPTY\n";
} else {
cout << line.back().first << '\n';
}
} else {
cout << line.size() << ' ' << tickets << '\n';
}
}
return 0;
}ข้อ 3 ชั้นวางขนมปัง
ร้านเบเกอรี่มีชั้นวางขนมปังยาวหนึ่งชั้น ขนมปังทุกชิ้นมีหมายเลขรอบอบติดไว้ ชั้นเรียงจากชิ้นที่อบนานที่สุดทางซ้าย ไปจนถึงชิ้นที่อบล่าสุดทางขวา ให้จำลองชั้นวางตามคำสั่ง Q คำสั่ง แต่ละคำสั่งเป็นแบบใดแบบหนึ่งต่อไปนี้ ตอนเริ่มชั้นว่าง
BAKE xขนมปังหมายเลข x เพิ่งอบเสร็จ วางไว้ขวาสุดของชั้นFOUND xพนักงานเจอขนมปังหมายเลข x ตกค้างอยู่หลังร้าน มันเก่ากว่าทุกชิ้นบนชั้น จึงวางไว้ซ้ายสุดของชั้นFRESHลูกค้าหยิบชิ้นที่ใหม่ที่สุด คือชิ้นขวาสุด ให้พิมพ์หมายเลขของมัน ถ้าชั้นว่างให้พิมพ์EMPTYSALEร้านขายลดราคาชิ้นที่เก่าที่สุด คือชิ้นซ้ายสุด ให้พิมพ์หมายเลขของมัน ถ้าชั้นว่างให้พิมพ์EMPTYCHECK iผู้จัดการถามว่าชิ้นที่ i นับจากซ้าย (ชิ้นซ้ายสุดคือชิ้นที่ 1) คือหมายเลขอะไร โดยไม่หยิบออก ถ้าชั้นมีขนมปังไม่ถึง i ชิ้นให้พิมพ์NONE
ทำครบทุกคำสั่งแล้ว ให้พิมพ์จำนวนขนมปังที่ยังเหลือบนชั้น
ข้อนี้ตั้งใจให้ฝึกย้ายวิธีจากข้อสำรับไพ่ของนักมายากลในบทที่ 14 มาใช้ deque ของ STL ที่ต่างคือต้องดูชิ้นที่ i นับจากซ้ายได้ด้วย
ข้อมูลเข้า
บรรทัดแรกมีจำนวนเต็ม Q โดยที่ 1 ≤ Q ≤ 200000
อีก Q บรรทัด บรรทัดละหนึ่งคำสั่งตามรูปแบบข้างบน โดยที่ 1 ≤ x ≤ 1000000000 และ 1 ≤ i ≤ 1000000000
ข้อมูลออก
สำหรับคำสั่ง FRESH SALE และ CHECK แต่ละคำสั่ง ให้พิมพ์คำตอบหนึ่งบรรทัดตามลำดับของคำสั่ง บรรทัดสุดท้ายพิมพ์จำนวนขนมปังที่เหลือบนชั้น
11
BAKE 101
BAKE 102
FOUND 95
CHECK 1
CHECK 3
CHECK 4
FRESH
SALE
BAKE 103
CHECK 2
SALE95
102
NONE
102
95
103
101
1ดูคำใบ้
ชั้นที่เพิ่มและหยิบได้ทั้งซ้ายและขวา แถมต้องดูชิ้นตรงกลางได้ คือ deque ในหน้า 17.2 ให้ซ้ายสุดเป็นหัว ขวาสุดเป็นท้าย ชิ้นที่ i นับจาก 1 คือ shelf[i - 1] เพราะ deque นับจาก 0 ก่อนดูต้องเช็กว่ามีถึง i ชิ้นไหม เพราะวงเล็บเหลี่ยมไม่ตรวจขอบให้ และเทียบ i กับ (int)shelf.size() ตามแบบที่ 1 ของการลองทำให้พังในหน้า 17.2 ถ้าใช้ vector แทน การเพิ่มหรือลบชิ้นซ้ายสุดทุกครั้งต้องเลื่อนทุกชิ้น ช้าลงตามจำนวนชิ้นกำลังสองแบบตารางในหน้า 17.2
ดูเฉลย
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int q;
cin >> q;
deque<int> shelf;
for (int k = 0; k < q; k++) {
string command;
cin >> command;
if (command == "BAKE") {
int x;
cin >> x;
shelf.push_back(x);
} else if (command == "FOUND") {
int x;
cin >> x;
shelf.push_front(x);
} else if (command == "FRESH") {
if (shelf.empty()) {
cout << "EMPTY\n";
} else {
cout << shelf.back() << '\n';
shelf.pop_back();
}
} else if (command == "SALE") {
if (shelf.empty()) {
cout << "EMPTY\n";
} else {
cout << shelf.front() << '\n';
shelf.pop_front();
}
} else {
int i;
cin >> i;
if (i > (int)shelf.size()) {
cout << "NONE\n";
} else {
cout << shelf[i - 1] << '\n';
}
}
}
cout << shelf.size() << '\n';
return 0;
}ข้อ 4 ปุ่ม undo
โปรแกรมพิมพ์ข้อความบนโทรศัพท์รุ่นหนึ่งพิมพ์ได้ทีละคำ และมีปุ่ม undo ให้ย้อนการแก้ไข ตอนเริ่มข้อความว่าง ผู้ใช้กดคำสั่ง Q ครั้ง แต่ละคำสั่งเป็นแบบใดแบบหนึ่งต่อไปนี้
TYPE wพิมพ์คำ w ต่อท้ายข้อความDELETEลบคำสุดท้ายของข้อความ ถ้าข้อความว่างอยู่ คำสั่งนี้ไม่มีผลอะไร และไม่นับเป็นการแก้ไขUNDOย้อนการแก้ไขครั้งล่าสุดที่ยังไม่ถูกย้อน การแก้ไขคือTYPEหรือDELETEที่มีผลจริง ย้อนTYPEคือเอาคำที่พิมพ์ออก ย้อนDELETEคือเอาคำที่ถูกลบกลับมาต่อท้าย กดUNDOติดกันหลายครั้งจะย้อนจากการแก้ไขล่าสุดไปหาเก่าสุดทีละครั้ง ถ้าไม่มีการแก้ไขเหลือให้ย้อน คำสั่งนี้ไม่มีผลอะไร คำสั่งUNDOเองไม่ใช่การแก้ไข จึงย้อนไม่ได้LASTพิมพ์คำสุดท้ายของข้อความ ถ้าข้อความว่างให้พิมพ์EMPTY
ทำครบทุกคำสั่งแล้ว ให้พิมพ์ข้อความทั้งหมด
ข้อมูลเข้า
บรรทัดแรกมีจำนวนเต็ม Q โดยที่ 1 ≤ Q ≤ 200000
อีก Q บรรทัด บรรทัดละหนึ่งคำสั่งตามรูปแบบข้างบน คำ w ยาว 1 ถึง 10 ตัวอักษร เป็นตัวอักษรภาษาอังกฤษตัวเล็กเท่านั้น
ข้อมูลออก
สำหรับคำสั่ง LAST แต่ละคำสั่ง ให้พิมพ์คำตอบหนึ่งบรรทัดตามลำดับ
บรรทัดสุดท้ายพิมพ์ข้อความทั้งหมด คำคั่นด้วยช่องว่างหนึ่งช่อง ถ้าข้อความว่างให้พิมพ์ EMPTY
9
TYPE i
TYPE like
TYPE cats
DELETE
TYPE dogs
LAST
UNDO
UNDO
LASTdogs
cats
i like catsดูคำใบ้
ข้อความเปลี่ยนแค่ที่ท้าย เก็บเป็น vector<string> แล้วใช้ push_back pop_back back ส่วนประวัติการแก้ไขเก็บใน stack ตามที่หน้า 17.3 เล่าไว้ ทุกครั้งที่แก้ ให้ push สิ่งที่ต้องรู้เพื่อย้อนการแก้นั้น ใช้ pair<char, string> ตัวแรกบอกว่าเป็น TYPE หรือ DELETE ตัวที่สองคือคำ สำหรับ DELETE ต้องจำคำที่ถูกลบไว้ก่อน pop_back ไม่งั้นจะเอากลับมาไม่ได้ อย่าบันทึก DELETE ที่ไม่มีผล และอย่าเก็บสำเนาข้อความทั้งก้อนทุกครั้งที่แก้ เพราะข้อความยาวได้เป็นแสนคำ
ดูเฉลย
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int q;
cin >> q;
vector<string> text;
stack<pair<char, string>> history;
for (int i = 0; i < q; i++) {
string command;
cin >> command;
if (command == "TYPE") {
string word;
cin >> word;
text.push_back(word);
history.push({'T', word});
} else if (command == "DELETE") {
if (!text.empty()) {
history.push({'D', text.back()});
text.pop_back();
}
} else if (command == "UNDO") {
if (!history.empty()) {
auto change = history.top();
history.pop();
if (change.first == 'T') {
text.pop_back();
} else {
text.push_back(change.second);
}
}
} else {
if (text.empty()) {
cout << "EMPTY\n";
} else {
cout << text.back() << '\n';
}
}
}
if (text.empty()) {
cout << "EMPTY\n";
}
for (int i = 0; i < (int)text.size(); i++) {
cout << text[i] << (i + 1 < (int)text.size() ? ' ' : '\n');
}
return 0;
}ข้อ 5 ปุ่ม back และ forward
เว็บเบราว์เซอร์มีปุ่ม back และปุ่ม forward ตอนเปิดโปรแกรม หน้าแรกที่เปิดอยู่คือ home ผู้ใช้ทำ Q อย่างตามลำดับ แต่ละอย่างเป็นแบบใดแบบหนึ่งต่อไปนี้
VISIT pเปิดหน้า p จากหน้าปัจจุบัน หน้าปัจจุบันถูกจำไว้ให้ back กลับมาได้ และประวัติ forward ทั้งหมดถูกล้างทิ้ง แม้ p จะเป็นชื่อเดียวกับหน้าปัจจุบันก็นับเป็นการเปิดหน้าใหม่BACKกลับไปหน้าล่าสุดที่จากมา หน้าปัจจุบันถูกจำไว้ให้ forward กลับมาได้ ถ้าไม่มีหน้าให้กลับ ไม่มีอะไรเกิดขึ้นFORWARDไปข้างหน้ากลับสู่หน้าล่าสุดที่เพิ่งกด back ออกมา หน้าปัจจุบันถูกจำไว้ให้ back กลับมาได้ ถ้าไม่มีหน้าให้ไป ไม่มีอะไรเกิดขึ้น
หลังทำแต่ละอย่าง ให้บอกว่าตอนนี้เปิดหน้าไหนอยู่
ข้อมูลเข้า
บรรทัดแรกมีจำนวนเต็ม Q โดยที่ 1 ≤ Q ≤ 200000
อีก Q บรรทัด บรรทัดละหนึ่งอย่างตามรูปแบบข้างบน ชื่อหน้า p ยาว 1 ถึง 20 ตัวอักษร ประกอบด้วยตัวอักษรภาษาอังกฤษตัวเล็ก ตัวเลข และจุด ไม่มีช่องว่าง
ข้อมูลออก
Q บรรทัด บรรทัดที่ i คือชื่อหน้าที่เปิดอยู่หลังทำอย่างที่ i
12
VISIT news
VISIT sport
VISIT football
BACK
BACK
FORWARD
VISIT tennis
FORWARD
BACK
BACK
BACK
BACKnews
sport
football
sport
news
sport
tennis
tennis
sport
news
home
homeดูคำใบ้
เริ่มจากโปรแกรม browser ในหน้า 17.3 แล้วเพิ่ม stack ตัวที่สองสำหรับ forward การกด back ย้ายหน้าปัจจุบันไปไว้บน stack forward ก่อน แล้วค่อยหยิบหน้าจาก stack back มาเป็นหน้าปัจจุบัน การกด forward ทำกลับกันทุกอย่าง จุดที่พลาดบ่อยที่สุดคือ VISIT ต้องล้าง stack forward ให้ว่าง stack ไม่มีคำสั่งล้างทีเดียว ให้ pop จนว่าง หรือกำหนดค่าใหม่เป็น stack<string>() การ pop ทีละตัวไม่ทำให้ช้า เพราะทุกหน้าถูก push เข้า stack forward ได้ก็ต่อเมื่อมีการกด back ซึ่งมีไม่เกิน Q ครั้ง
ดูเฉลย
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int q;
cin >> q;
stack<string> back_pages, forward_pages;
string current = "home";
for (int i = 0; i < q; i++) {
string command;
cin >> command;
if (command == "VISIT") {
string page;
cin >> page;
back_pages.push(current);
current = page;
while (!forward_pages.empty()) {
forward_pages.pop();
}
} else if (command == "BACK") {
if (!back_pages.empty()) {
forward_pages.push(current);
current = back_pages.top();
back_pages.pop();
}
} else {
if (!forward_pages.empty()) {
back_pages.push(current);
current = forward_pages.top();
forward_pages.pop();
}
}
cout << current << '\n';
}
return 0;
}ข้อ 6 ลูกแก้วสีเดียวกัน
ในเกมลูกแก้วเกมหนึ่ง ลูกแก้วหลายสีวางเรียงกันเป็นแถวเดียวบนราง ถ้ามีลูกแก้วสองลูกที่อยู่ติดกันและสีเดียวกัน ทั้งสองลูกจะแตกหายไป แล้วลูกที่อยู่ทางซ้ายและทางขวาของคู่นั้นเลื่อนมาชิดกัน ซึ่งอาจทำให้เกิดคู่สีเดียวกันคู่ใหม่ แตกต่อกันไปเรื่อยๆ จนไม่มีลูกแก้วสองลูกที่ติดกันสีเดียวกันเหลืออยู่
ถ้ามีหลายคู่ให้แตกพร้อมกัน จะให้คู่ไหนแตกก่อนก็ได้ผลสุดท้ายเหมือนกันเสมอ ให้หาว่าลูกแก้วแตกไปทั้งหมดกี่คู่ และเหลือลูกแก้วสีอะไรบ้างเรียงจากซ้ายไปขวา
ตัวอย่างเช่น abccbd คู่ cc แตกก่อนเหลือ abbd แล้วคู่ bb ที่เพิ่งมาชิดกันแตกต่อ เหลือ ad รวมแตกไป 2 คู่
ข้อมูลเข้า
บรรทัดเดียว เป็นสีของลูกแก้วเรียงจากซ้ายไปขวา แต่ละสีแทนด้วยตัวอักษรภาษาอังกฤษตัวเล็กหนึ่งตัว ยาว 1 ถึง 1000000 ตัวอักษร
ข้อมูลออก
บรรทัดแรกพิมพ์จำนวนคู่ที่แตก
บรรทัดที่สองพิมพ์สีของลูกแก้วที่เหลือเรียงจากซ้ายไปขวาติดกันโดยไม่มีช่องว่าง ถ้าไม่เหลือลูกแก้วเลยให้พิมพ์ EMPTY
abccbd2
adxyyxzz3
EMPTYดูคำใบ้
ถ้าวนหาคู่ที่ติดกันตั้งแต่ซ้ายสุด ลบด้วย erase แล้วเริ่มหาใหม่ แถวแบบ abc...cba ที่ยาวล้านลูกจะแตกจากตรงกลางทีละคู่ ทุกคู่ต้องเดินหาจากซ้ายสุดไปถึงกลางแถวแล้วเลื่อนครึ่งหลัง รวมราว 2.5 × 1011 ก้าว ไม่ทัน ให้คิดว่าวางลูกแก้วลงรางใหม่ทีละลูกจากซ้ายไปขวา ลูกที่วางแล้วและยังไม่แตกเป็น stack ลูกใหม่แตกได้กับลูกขวาสุดที่วางไว้เท่านั้น ซึ่งก็คือตัวบนสุด ถ้าสีเดียวกันให้ pop ไม่อย่างนั้นให้ push ทุกลูกถูก push และ pop อย่างละไม่เกินหนึ่งครั้ง ตอนจบต้องพิมพ์จากก้น stack ขึ้นมา ซึ่ง stack ทำไม่ได้ ให้ใช้ string เป็น stack แทนด้วย push_back pop_back และ back แบบเดียวกับที่หน้า 17.1 บอกว่า vector ใช้เป็น stack ได้ string ใช้คำสั่งสามตัวนี้ได้เหมือน vector ทุกอย่าง เพราะ string เก็บตัวอักษรเรียงต่อกันแบบเดียวกับ vector<char> เช่น s.push_back('r') ต่อตัวอักษรหนึ่งตัวท้ายข้อความ แล้วพิมพ์ทั้งข้อความได้ด้วย cout << s ตามปกติ
ดูเฉลย
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
string marbles;
cin >> marbles;
string rail;
int pairs = 0;
for (char c : marbles) {
if (!rail.empty() && rail.back() == c) {
rail.pop_back();
pairs++;
} else {
rail.push_back(c);
}
}
cout << pairs << '\n';
if (rail.empty()) {
cout << "EMPTY\n";
} else {
cout << rail << '\n';
}
return 0;
}ข้อ 7 เครื่องพิมพ์ผลัดกันพิมพ์
ห้องคอมพิวเตอร์มีเครื่องพิมพ์ใช้ร่วมกันหนึ่งเครื่อง วันนี้มีงานพิมพ์ส่งมา N งาน งานที่ i ส่งมาถึงที่วินาทีที่ ai และยาว pi หน้า เครื่องพิมพ์ได้วินาทีละหนึ่งหน้า เพื่อไม่ให้งานยาวงานเดียวกันคนอื่นไว้นาน เครื่องจะพิมพ์งานหนึ่งติดต่อกันได้ไม่เกิน K หน้าต่อรอบ แล้วต้องสลับไปงานอื่น เครื่องทำงานตามกฎนี้
- งานที่ส่งมาถึงจะต่อท้ายคิว ถ้าหลายงานมาถึงเวลาเดียวกัน งานที่อยู่ก่อนในข้อมูลเข้าต่อคิวก่อน
- เมื่อเครื่องว่าง ถ้าคิวมีงาน เครื่องหยิบงานหัวคิวมาพิมพ์ติดต่อกัน K หน้า หรือเท่าที่งานนั้นเหลือถ้าเหลือน้อยกว่า K หน้า ถ้าคิวว่าง เครื่องรอจนกว่าจะมีงานส่งมาถึง
- เมื่อพิมพ์จบหนึ่งรอบที่วินาทีที่ T ให้งานทุกงานที่ส่งมาถึงภายในวินาทีที่ T รวมถึงที่มาถึงตรงวินาทีที่ T พอดี ต่อท้ายคิวให้เรียบร้อยก่อน จากนั้นถ้างานที่เพิ่งพิมพ์ยังเหลือหน้า งานนั้นกลับไปต่อท้ายคิว ถ้าไม่เหลือแล้ว งานนั้นเสร็จที่วินาทีที่ T
ให้หาว่างานแต่ละงานเสร็จที่วินาทีที่เท่าไร
ข้อมูลเข้า
บรรทัดแรกมีจำนวนเต็ม N และ K คั่นด้วยช่องว่าง โดยที่ 1 ≤ N ≤ 100000 และ 1 ≤ K ≤ 1000000000
อีก N บรรทัด บรรทัดที่ i มีจำนวนเต็ม ai และ pi คั่นด้วยช่องว่าง โดยที่ 0 ≤ a1 ≤ a2 ≤ ... ≤ aN ≤ 1000000000 และ 1 ≤ pi ≤ 1000000000
รับประกันว่าจำนวนรอบที่เครื่องพิมพ์ทั้งหมดรวมกันไม่เกิน 2000000 รอบ
ข้อมูลออก
N บรรทัด บรรทัดที่ i คือวินาทีที่งานที่ i พิมพ์เสร็จ
4 3
0 5
1 2
2 4
8 110
5
12
112 5
0 3
10 43
14ดูคำใบ้
เริ่มจากโปรแกรม game-turns ในหน้า 17.4 แต่ให้ queue เก็บหมายเลขงาน ส่วนหน้าที่เหลือเก็บในอาเรย์แยก จะได้เขียนเวลาเสร็จลงช่องของงานนั้นได้ เพิ่มตัวแปร next ชี้งานถัดไปที่ยังไม่มาถึง ทุกครั้งที่เวลาเดินไป ให้วนเอางานที่ arrive[next] <= now ต่อท้ายคิว ลำดับในกฎข้อสามสำคัญ งานที่มาถึงต้องต่อคิวก่อนงานที่เพิ่งพิมพ์ไม่จบ ถ้าคิวว่างแต่ยังมีงานไม่เสร็จ ให้กระโดดเวลาไปที่งานถัดไปมาถึงเลย อย่าเดินทีละวินาที เวลาเกิน int ได้เพราะงานยาวพันล้านหน้าเรียงกันได้แสนงาน ใช้ long long แต่ละรอบใช้คำสั่งของ queue ไม่กี่ตัว งานรวมจึงโตตามจำนวนรอบซึ่งไม่เกินสองล้าน
ดูเฉลย
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, k;
cin >> n >> k;
vector<int> arrive(n), pages_left(n);
for (int i = 0; i < n; i++) {
cin >> arrive[i] >> pages_left[i];
}
vector<long long> finish(n);
queue<int> waiting;
long long now = 0;
int next = 0;
int done = 0;
while (done < n) {
if (waiting.empty()) {
now = max(now, (long long)arrive[next]);
}
while (next < n && arrive[next] <= now) {
waiting.push(next);
next++;
}
int job = waiting.front();
waiting.pop();
int printed = min(pages_left[job], k);
now += printed;
pages_left[job] -= printed;
while (next < n && arrive[next] <= now) {
waiting.push(next);
next++;
}
if (pages_left[job] > 0) {
waiting.push(job);
} else {
finish[job] = now;
done++;
}
}
for (int i = 0; i < n; i++) {
cout << finish[i] << '\n';
}
return 0;
}ข้อ 8 สูงสุดในรอบกี่วัน
เขื่อนแห่งหนึ่งวัดระดับน้ำวันละครั้ง ติดต่อกัน N วัน วันที่ i วัดได้ hi มิลลิเมตร ข่าวชอบพาดหัวว่า "ระดับน้ำวันนี้สูงสุดในรอบ X วัน" ซึ่งหมายถึงวันนี้กับอีก X - 1 วันก่อนหน้าที่ติดกัน รวม X วัน ทุกวันในช่วงนั้นมีระดับน้ำไม่เกินระดับของวันนี้ และ X มากที่สุดเท่าที่เป็นไปได้ ถ้าเมื่อวานระดับน้ำสูงกว่าวันนี้ X จะเป็น 1 เพราะนับแค่วันนี้วันเดียว
ให้หา X ของทุกวัน
ข้อนี้กลับด้านของข้อตึกที่สูงกว่าหลังแรกในบทที่ 14 ข้อนั้นมองไปทางขวา ข้อนี้มองย้อนไปทางซ้าย และนับวันที่สูงเท่ากันรวมในช่วงด้วย ตั้งใจให้ฝึกเขียน monotonic stack ด้วย stack ของ STL แล้วระวังเครื่องหมายเทียบให้ตรงกับโจทย์
ข้อมูลเข้า
บรรทัดแรกมีจำนวนเต็ม N โดยที่ 1 ≤ N ≤ 1000000
บรรทัดที่สองมีจำนวนเต็ม N ตัว คือ h1 ถึง hN คั่นด้วยช่องว่าง โดยที่ 1 ≤ hi ≤ 1000000000
ข้อมูลออก
บรรทัดเดียว มีจำนวนเต็ม N ตัวคั่นด้วยช่องว่างหนึ่งช่อง ตัวที่ i คือ X ของวันที่ i
8
120 110 115 130 125 125 140 1001 1 2 4 1 2 7 1ดูคำใบ้
ถ้าทุกวันเดินย้อนกลับไปจนเจอวันที่สูงกว่า เมื่อระดับน้ำสูงขึ้นทุกวัน วันสุดท้ายต้องย้อนเกือบล้านวัน รวมราว 5 × 1011 ก้าว ไม่ทัน สิ่งที่ต้องหาจริงๆ คือวันล่าสุดก่อนหน้าที่สูงกว่าวันนี้อย่างเคร่งครัด ถ้าวันนั้นคือวันที่ j คำตอบคือ i - j และถ้าไม่มีเลย คำตอบคือ i + 1 เมื่อนับวันจาก 0 นี่คือโจทย์วันแรกที่ร้อนกว่าในหน้า 17.3 แต่มองย้อนไปทางซ้าย ใช้ monotonic stack เก็บเลขวัน เดินจากซ้ายไปขวา ก่อนตอบวันที่ i ให้ pop วันที่ไม่สูงกว่าวันที่ i ทิ้งให้หมด เพราะวันพวกนี้ไม่มีวันเป็นวันที่สูงกว่าของวันหลังจากนี้อีก ตัวบนสุดที่เหลือคือวันที่ต้องการ แล้วค่อย push i วันที่ระดับน้ำเท่ากันนับรวมในช่วงด้วย ระวังเครื่องหมาย < กับ <= ให้ตรงกับข้อนี้
ดูเฉลย
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
vector<int> level(n);
for (int i = 0; i < n; i++) {
cin >> level[i];
}
stack<int> higher;
for (int i = 0; i < n; i++) {
while (!higher.empty() && level[higher.top()] <= level[i]) {
higher.pop();
}
int days;
if (higher.empty()) {
days = i + 1;
} else {
days = i - higher.top();
}
cout << days << (i + 1 < n ? ' ' : '\n');
higher.push(i);
}
return 0;
}ข้อ 9 สะพานแกว่ง
เครื่องวัดแรงสั่นสะเทือนบนสะพานบันทึกค่าเป็นจำนวนเต็มทุกมิลลิวินาที รวม N ค่า ค่าติดลบได้ เพราะสะพานแกว่งได้ทั้งสองทาง วิศวกรอยากรู้ว่าในทุกช่วง K มิลลิวินาทีติดกัน สะพานแกว่งกว้างแค่ไหน ความกว้างของการแกว่งในช่วงหนึ่งคือค่ามากสุดลบด้วยค่าน้อยสุดในช่วงนั้น
ให้หาความกว้างของการแกว่งของทุกช่วง ตั้งแต่ช่วงที่เริ่มที่ค่าแรก ไปจนถึงช่วงที่จบที่ค่าสุดท้าย รวม N - K + 1 ช่วง
ข้อมูลเข้า
บรรทัดแรกมีจำนวนเต็ม N และ K คั่นด้วยช่องว่าง โดยที่ 1 ≤ K ≤ N ≤ 1000000
บรรทัดที่สองมีจำนวนเต็ม N ตัวคั่นด้วยช่องว่าง แต่ละตัวมีค่าตั้งแต่ -2000000000 ถึง 2000000000
ข้อมูลออก
บรรทัดเดียว มีจำนวนเต็ม N - K + 1 ตัวคั่นด้วยช่องว่างหนึ่งช่อง ตัวที่ i คือความกว้างของการแกว่งในช่วงที่เริ่มที่ค่าที่ i
8 3
5 -2 7 7 1 -6 3 29 9 6 13 9 9ดูคำใบ้
ไล่หาค่ามากสุดและน้อยสุดใหม่ทุกช่วง เมื่อ N เป็นล้านและ K เป็นห้าแสน ต้องดูราว 2.5 × 1011 ค่า ไม่ทัน ใช้ monotonic deque แบบโปรแกรม window-deque ในหน้า 17.4 สองตัว ตัวหนึ่งหาค่ามากสุด ค่าเรียงจากหัวไปท้ายไม่เพิ่มขึ้น อีกตัวหาค่าน้อยสุด ค่าเรียงจากหัวไปท้ายไม่ลดลง ตัวหลังเขียนเหมือนตัวแรกทุกอย่าง แค่กลับเครื่องหมายเทียบตอนทิ้งท้าย ทั้งสองตัวต้องทิ้งหัวที่หลุดหน้าต่างด้วย ระวังคำตอบ ค่ามากสุด 2 × 109 ลบค่าน้อยสุด -2 × 109 ได้ 4 × 109 เกิน int ต้องแปลงเป็น long long ก่อนลบ
ดูเฉลย
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, k;
cin >> n >> k;
vector<int> value(n);
for (int i = 0; i < n; i++) {
cin >> value[i];
}
deque<int> high, low;
for (int i = 0; i < n; i++) {
while (!high.empty() && value[high.back()] <= value[i]) {
high.pop_back();
}
high.push_back(i);
while (!low.empty() && value[low.back()] >= value[i]) {
low.pop_back();
}
low.push_back(i);
if (high.front() <= i - k) {
high.pop_front();
}
if (low.front() <= i - k) {
low.pop_front();
}
if (i >= k - 1) {
long long swing = (long long)value[high.front()] - value[low.front()];
cout << swing << (i + 1 < n ? ' ' : '\n');
}
}
return 0;
}ข้อ 10 ภาพวาดบนรั้วไม้
รั้วข้างโรงเรียนทำจากแผ่นไม้ N แผ่นเรียงชิดกันเป็นแถว ทุกแผ่นกว้าง 1 เมตรและตั้งอยู่บนพื้นระดับเดียวกัน แผ่นที่ i สูง hi เมตร ชมรมศิลปะอยากวาดภาพสี่เหลี่ยมผืนผ้าหนึ่งภาพบนรั้วให้ใหญ่ที่สุด ภาพต้องมีขอบล่างอยู่ที่พื้น ขอบซ้ายและขวาอยู่ตรงรอยต่อระหว่างแผ่นไม้หรือที่ปลายรั้ว และทุกส่วนของภาพต้องอยู่บนแผ่นไม้ ภาพจึงสูงได้ไม่เกินแผ่นที่เตี้ยที่สุดที่ภาพทับอยู่
ให้หาพื้นที่มากที่สุดของภาพเป็นตารางเมตร
ข้อมูลเข้า
บรรทัดแรกมีจำนวนเต็ม N โดยที่ 1 ≤ N ≤ 1000000
บรรทัดที่สองมีจำนวนเต็ม N ตัว คือ h1 ถึง hN คั่นด้วยช่องว่าง โดยที่ 1 ≤ hi ≤ 1000000000
ข้อมูลออก
พื้นที่มากที่สุดของภาพ
7
3 1 4 4 6 2 5121
10000000001000000000ดูคำใบ้
ภาพที่ใหญ่ที่สุดต้องสูงเท่ากับแผ่นที่เตี้ยที่สุดที่มันทับพอดี ไม่อย่างนั้นยืดให้สูงขึ้นได้อีก จึงลองให้แผ่น i แต่ละแผ่นเป็นแผ่นที่เตี้ยที่สุดของภาพ แล้วขยายภาพไปทางซ้ายและขวาจนชนแผ่นแรกที่เตี้ยกว่าแผ่น i ถ้าเดินขยายทีละแผ่น รั้วที่สูงเท่ากันทั้งล้านแผ่นต้องเดินราว 1012 ก้าว ไม่ทัน แผ่นแรกทางซ้ายที่เตี้ยกว่า กับแผ่นแรกทางขวาที่เตี้ยกว่า คือโจทย์แบบเดียวกับวันแรกที่ร้อนกว่าในหน้า 17.3 แต่กลับเป็นหาตัวที่น้อยกว่า ใช้ monotonic stack สองรอบ รอบแรกเดินจากซ้ายไปขวาได้แผ่นที่เตี้ยกว่าทางซ้าย รอบสองเดินจากขวาไปซ้ายได้แผ่นที่เตี้ยกว่าทางขวา ถ้าทางซ้ายเป็นแผ่น L และทางขวาเป็นแผ่น R ภาพกว้าง R - L - 1 ถ้าไม่มีให้ถือว่า L เป็น -1 และ R เป็น N พื้นที่ใหญ่ถึง 106 × 109 ต้องคูณเป็น long long
ดูเฉลย
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
vector<int> height(n);
for (int i = 0; i < n; i++) {
cin >> height[i];
}
vector<int> left_lower(n), right_lower(n);
stack<int> boards;
for (int i = 0; i < n; i++) {
while (!boards.empty() && height[boards.top()] >= height[i]) {
boards.pop();
}
if (boards.empty()) {
left_lower[i] = -1;
} else {
left_lower[i] = boards.top();
}
boards.push(i);
}
while (!boards.empty()) {
boards.pop();
}
for (int i = n - 1; i >= 0; i--) {
while (!boards.empty() && height[boards.top()] >= height[i]) {
boards.pop();
}
if (boards.empty()) {
right_lower[i] = n;
} else {
right_lower[i] = boards.top();
}
boards.push(i);
}
long long best = 0;
for (int i = 0; i < n; i++) {
long long width = right_lower[i] - left_lower[i] - 1;
best = max(best, width * height[i]);
}
cout << best << '\n';
return 0;
}