18.6 แบบฝึกหัด
เขียนแต่ละข้อเป็นไฟล์ใหม่ใน Code::Blocks รันแล้วเทียบกับผลลัพธ์ที่ต้องได้ให้ตรงทุกตัวอักษร รวมถึงช่องว่างและการขึ้นบรรทัด ติดจริงๆ ค่อยเปิดคำใบ้ และเปิดเฉลยหลังทำเสร็จแล้วเท่านั้น
ข้อ 1 นกที่พบในช่วงแรก
ชมรมดูนกของโรงเรียนออกสำรวจทุกวัน N วัน วันที่ i จดรหัสชนิดของนกตัวแรกที่เห็นในวันนั้นไว้ นกชนิดเดียวกันอาจถูกจดซ้ำได้หลายวัน ประธานชมรมมีคำถาม Q ข้อ แต่ละข้อถามว่า ตั้งแต่วันที่ 1 ถึงวันที่ k ชมรมเจอนกกี่ชนิดที่ไม่ซ้ำกัน
ข้อมูลเข้า
บรรทัดแรกมีจำนวนเต็ม N และ Q คั่นด้วยช่องว่าง โดยที่ 1 ≤ N, Q ≤ 200000
บรรทัดที่สองมีรหัสชนิดนกของวันที่ 1 ถึงวันที่ N คั่นด้วยช่องว่าง แต่ละรหัสเป็นจำนวนเต็มตั้งแต่ 1 ถึง 1000000
อีก Q บรรทัด บรรทัดละหนึ่งคำถาม มีจำนวนเต็ม k โดยที่ 1 ≤ k ≤ N
ข้อมูลออก
Q บรรทัด ตอบคำถามตามลำดับ บรรทัดละหนึ่งข้อ เป็นจำนวนชนิดนกที่ไม่ซ้ำกันตั้งแต่วันที่ 1 ถึงวันที่ k
6 5
7 3 7 12 3 5
1
3
4
6
21
2
3
4
23 3
1000000 1000000 1
2
3
11
2
1ดูคำใบ้
ถ้าตอบแต่ละคำถามด้วยการไล่ k วันแรกใหม่ งานรวมคือ O(NQ) ซึ่งเป็น 4 × 1010 ไม่ทัน ให้เตรียมคำตอบของทุก k ไว้ก่อนในรอบเดียว ไล่วันที่ 1 ถึง N ใช้อาเรย์จำว่าชนิดไหนเคยเห็นแล้วแบบหน้า 5.3 นับชนิดใหม่สะสมไป แล้วเก็บจำนวนชนิดหลังวันที่ k ไว้ในช่องที่ k จากนั้นตอบคำถามละ O(1) รวมเป็น O(N + Q)
ดูเฉลย
#include <bits/stdc++.h>
using namespace std;
bool seen[1000001];
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, q;
cin >> n >> q;
vector<int> kinds(n + 1);
kinds[0] = 0;
for (int i = 1; i <= n; i++) {
int b;
cin >> b;
kinds[i] = kinds[i - 1];
if (!seen[b]) {
seen[b] = true;
kinds[i]++;
}
}
for (int t = 0; t < q; t++) {
int k;
cin >> k;
cout << kinds[k] << '\n';
}
return 0;
}ข้อ 2 คิวร้านขนม
ร้านขนมหน้าโรงเรียนมีคิวเดียว ช่วงพักกลางวันมีเหตุการณ์เกิดขึ้น N ครั้งตามลำดับ แต่ละครั้งเป็นอย่างใดอย่างหนึ่ง
1 xนักเรียนเลขประจำตัว x มาต่อท้ายคิว2แม่ค้าขายขนมให้คนที่อยู่หัวคิว แล้วคนนั้นออกจากคิวไป
ทุกครั้งที่ขาย ให้พิมพ์เลขประจำตัวของคนที่ได้ขนม ถ้าตอนนั้นไม่มีใครในคิว ให้พิมพ์ empty แทน
ข้อมูลเข้า
บรรทัดแรกมีจำนวนเต็ม N โดยที่ 1 ≤ N ≤ 1000000
อีก N บรรทัด แต่ละบรรทัดเป็นเหตุการณ์ในรูปแบบข้างบน เลขประจำตัว x เป็นจำนวนเต็มตั้งแต่ 1 ถึง 1000000000 รับประกันว่ามีเหตุการณ์ 2 อย่างน้อยหนึ่งครั้ง
ข้อมูลออก
หนึ่งบรรทัดต่อเหตุการณ์ 2 หนึ่งครั้ง ตามลำดับ เป็นเลขประจำตัวของคนที่ได้ขนม หรือ empty
7
1 6701
1 6702
2
1 6815
2
2
26701
6702
6815
empty3
2
1 1000000000
2empty
1000000000ดูคำใบ้
ถ้าเก็บคิวใน vector แล้วลบหัวคิวด้วย erase(v.begin()) ทุกครั้งที่ลบต้องเลื่อนคนที่เหลือทั้งแถว เมื่อคิวยาวหลายแสนคนจะไม่ทัน ใช้ queue จากบทที่ 17 ที่ push และ pop ได้ใน O(1) หรือเก็บใน vector แล้วจำตำแหน่งหัวคิวไว้ในตัวแปร แบบในหน้า 18.4
ดูเฉลย
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
queue<int> line;
for (int i = 0; i < n; i++) {
int type;
cin >> type;
if (type == 1) {
int x;
cin >> x;
line.push(x);
} else if (line.empty()) {
cout << "empty\n";
} else {
cout << line.front() << '\n';
line.pop();
}
}
return 0;
}ข้อ 3 คู่คะแนนพอดี
ครูจะจับนักเรียน N คนเป็นคู่ทำโครงงาน และอยากให้คะแนนเก็บของสองคนในคู่รวมกันได้ K พอดี เพื่อให้ทุกคู่มีกำลังเท่ากัน ให้หาว่ามีคู่ของนักเรียนสองคนที่ต่างกันกี่คู่ที่คะแนนรวมเท่ากับ K คู่ของคนที่ i กับคนที่ j นับเป็นคู่เดียวกับคู่ของคนที่ j กับคนที่ i
ข้อมูลเข้า
บรรทัดแรกมีจำนวนเต็ม N และ K คั่นด้วยช่องว่าง โดยที่ 1 ≤ N ≤ 200000 และ 2 ≤ K ≤ 2000000
บรรทัดที่สองมีคะแนนของนักเรียน N คน คั่นด้วยช่องว่าง แต่ละค่าเป็นจำนวนเต็มตั้งแต่ 1 ถึง 1000000
ข้อมูลออก
จำนวนคู่ที่คะแนนรวมเท่ากับ K
6 100
30 70 55 45 70 2035 10
5 5 5 3 74ดูคำใบ้
ไล่นักเรียนทีละคนจากซ้ายไปขวา คนที่มีคะแนน x จับคู่กับคนก่อนหน้าที่มีคะแนน K − x ได้ทุกคน ถ้าเก็บอาเรย์นับว่าคะแนนแต่ละค่าเคยเห็นมาแล้วกี่คนแบบหน้า 5.3 ก็บวกจำนวนนั้นได้ทันที แล้วค่อยนับคนปัจจุบันเพิ่ม รวมเป็น O(N) ระวังสองเรื่อง K − x อาจน้อยกว่า 1 หรือเกิน 1000000 ซึ่งอยู่นอกอาเรย์ และจำนวนคู่ใหญ่ได้ถึงราว 2 × 1010 ต้องใช้ long long
ดูเฉลย
#include <bits/stdc++.h>
using namespace std;
#define MAX_SCORE 1000000
int seen[MAX_SCORE + 1];
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, k;
cin >> n >> k;
long long pairs = 0;
for (int i = 0; i < n; i++) {
int x;
cin >> x;
int need = k - x;
if (need >= 1 && need <= MAX_SCORE) {
pairs += seen[need];
}
seen[x]++;
}
cout << pairs << '\n';
return 0;
}ข้อ 4 สองชิ้นพอดีงบ
ร้านขายอุปกรณ์มือถือเรียงสินค้า N ชิ้นตามราคาจากถูกไปแพง ไม่มีสินค้าสองชิ้นไหนราคาเท่ากัน น้องมีเงิน K บาท และอยากซื้อสินค้าสองชิ้นที่ต่างกันโดยใช้เงินหมดพอดี ให้หาว่ามีวิธีเลือกกี่วิธี การเลือกชิ้นที่ i กับชิ้นที่ j นับเป็นวิธีเดียวกับการเลือกชิ้นที่ j กับชิ้นที่ i
ข้อมูลเข้า
บรรทัดแรกมีจำนวนเต็ม N และ K คั่นด้วยช่องว่าง โดยที่ 1 ≤ N ≤ 200000 และ 2 ≤ K ≤ 4000000000
บรรทัดที่สองมีราคา N ค่า เรียงจากน้อยไปมาก ไม่มีค่าซ้ำ คั่นด้วยช่องว่าง แต่ละค่าเป็นจำนวนเต็มตั้งแต่ 1 ถึง 2000000000
ข้อมูลออก
จำนวนวิธีเลือกสินค้าสองชิ้นที่ราคารวมเท่ากับ K
7 1000
150 300 450 550 700 850 99035 20
1 5 10 15 192ดูคำใบ้
ข้อ 3 ใช้อาเรย์นับได้เพราะค่าไม่เกิน 106 ข้อนี้ราคาถึง 2 × 109 ใช้วิธีนั้นไม่ได้ แต่ราคาเรียงมาแล้วและไม่ซ้ำ ใช้สองตัวชี้ i เริ่มที่ชิ้นถูกสุด j เริ่มที่ชิ้นแพงสุด ถ้ารวมกันน้อยกว่า K ชิ้น i จับคู่กับชิ้นไหนที่เหลือก็ไม่พอ ให้เลื่อน i ไปทางขวา ถ้ามากกว่า K ให้เลื่อน j ไปทางซ้าย ถ้าพอดีให้นับแล้วเลื่อนทั้งคู่ ทำจน i กับ j มาเจอกัน เป็น O(N) ระวังราคาสองชิ้นรวมกันได้ถึงเกือบ 4 × 109 เกิน int และ K ก็เกิน int ได้
ดูเฉลย
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
long long k;
cin >> n >> k;
vector<long long> price(n);
for (int i = 0; i < n; i++) {
cin >> price[i];
}
int ways = 0;
int i = 0, j = n - 1;
while (i < j) {
long long total = price[i] + price[j];
if (total == k) {
ways++;
i++;
j--;
} else if (total < k) {
i++;
} else {
j--;
}
}
cout << ways << '\n';
return 0;
}ข้อ 5 นับรอบลูปคูณสอง
น้องอยากรู้ว่าโค้ดนี้ทำ count++ กี่ครั้ง แต่ถ้ารันตรงๆ เมื่อ N ใหญ่จะรอไม่ไหว ให้เขียนโปรแกรมที่ตอบจำนวนนั้นได้เร็ว
long long count = 0;
for (long long i = 1; i <= N; i++) {
for (long long j = 1; j <= i; j *= 2) {
count++;
}
}
ตัวอย่างเช่น N = 5 รอบที่ i = 1 ลูปในวน 1 รอบ i = 2 และ 3 วน 2 รอบ i = 4 และ 5 วน 3 รอบ รวมเป็น 1 + 2 + 2 + 3 + 3 = 11
ข้อมูลเข้า
บรรทัดเดียว มีจำนวนเต็ม N โดยที่ 1 ≤ N ≤ 1000000000000000 (1015)
ข้อมูลออก
จำนวนครั้งที่ทำ count++
5111654ดูคำใบ้
ลูปในของรอบที่ i วนเท่ากับจำนวนหลักของ i เมื่อเขียนเป็นเลขฐานสอง i ที่ 2b−1 ≤ i ≤ 2b − 1 วนลูปในเท่ากันคือ b รอบ และมี 2b−1 ตัว แบ่ง 1 ถึง N เป็นกลุ่มตาม b แล้วบวก b × จำนวนตัวในกลุ่ม กลุ่มสุดท้ายถูก N ตัดกลางทาง ต้องนับแค่ถึง N มีไม่เกินราว 50 กลุ่ม จึงเป็น O(log N)
ดูเฉลย
#include <bits/stdc++.h>
using namespace std;
int main()
{
long long n;
cin >> n;
long long count = 0;
long long low = 1;
for (int bits = 1; low <= n; bits++) {
long long high = min(2 * low - 1, n);
count += bits * (high - low + 1);
low *= 2;
}
cout << count << '\n';
return 0;
}ข้อ 6 บีกเกอร์ตวงพอดี
ห้องแล็บมีบีกเกอร์ N ใบ ใบที่ i จุน้ำได้ ai มิลลิลิตร ครูให้เลือกบีกเกอร์สองใบที่ต่างกัน ใบแรกใช้ตักน้ำจนเต็มแล้วเทใส่ใบที่สองซ้ำหลายรอบ ถ้าความจุของใบที่สองหารด้วยความจุของใบแรกลงตัว ใบที่สองจะเต็มพอดีโดยน้ำไม่ล้น ให้นับว่ามีวิธีเลือกใบตักกับใบรับแบบนี้กี่วิธี
การเลือกใบที่ i ตักแล้วใบที่ j รับ ต่างจากใบที่ j ตักแล้วใบที่ i รับ ถ้าสองใบจุเท่ากัน ทั้งสองแบบใช้ได้และนับเป็นสองวิธี
ข้อมูลเข้า
บรรทัดแรกมีจำนวนเต็ม N โดยที่ 2 ≤ N ≤ 200000
บรรทัดที่สองมีความจุ a1 ถึง aN คั่นด้วยช่องว่าง แต่ละค่าเป็นจำนวนเต็มตั้งแต่ 1 ถึง 1000000
ข้อมูลออก
จำนวนวิธีเลือกใบตักกับใบรับที่เทแล้วเต็มพอดี
5
50 100 250 30 50053
200 200 6004ดูคำใบ้
ไล่ทุกคู่ต้องหาร N(N − 1) ครั้ง ราว 4 × 1010 ไม่ทัน ความจุไม่เกิน 106 จึงนับด้วยอาเรย์ได้ว่าความจุแต่ละค่ามีกี่ใบ แล้วกลับด้านคำถาม แทนที่จะถามว่าใบที่รับหารด้วยอะไรลงตัว ให้ไล่ความจุของใบที่ตัก d แล้วกระโดดไปทุกพหุคูณ 2d 3d ... จนถึง 106 จำนวนรอบรวมคือ 106/1 + 106/2 + ... ซึ่งเป็น O(V log V) แบบลูป harmonic ในหน้า 18.3 คู่ที่ความจุเท่ากันต้องนับแยก เพราะห้ามใช้ใบเดียวกันทั้งตักและรับ คำตอบเกิน int ได้
ดูเฉลย
#include <bits/stdc++.h>
using namespace std;
#define MAX_VOLUME 1000000
long long how_many[MAX_VOLUME + 1];
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
for (int i = 0; i < n; i++) {
int v;
cin >> v;
how_many[v]++;
}
long long ways = 0;
for (int d = 1; d <= MAX_VOLUME; d++) {
if (how_many[d] == 0) {
continue;
}
ways += how_many[d] * (how_many[d] - 1);
for (int m = 2 * d; m <= MAX_VOLUME; m += d) {
ways += how_many[d] * how_many[m];
}
}
cout << ways << '\n';
return 0;
}ข้อ 7 ของขวัญสามชิ้น
ร้านของขวัญวางสินค้าเรียงเป็นแถว N ชิ้น ชิ้นที่ i ราคา pi บาท น้องมีเงิน K บาท และอยากซื้อของสามชิ้นที่ต่างกัน โดยใช้เงินหมดพอดี ให้หาว่ามีวิธีเลือกของสามชิ้นกี่แบบ การเลือกชิ้นเดียวกันในลำดับต่างกันนับเป็นแบบเดียว ของสองชิ้นที่ราคาเท่ากันนับเป็นคนละชิ้น
ข้อมูลเข้า
บรรทัดแรกมีจำนวนเต็ม N และ K คั่นด้วยช่องว่าง โดยที่ 3 ≤ N ≤ 5000 และ 3 ≤ K ≤ 3000000
บรรทัดที่สองมีราคา p1 ถึง pN คั่นด้วยช่องว่าง แต่ละค่าเป็นจำนวนเต็มตั้งแต่ 1 ถึง 1000000
ข้อมูลออก
จำนวนแบบของการเลือกของสามชิ้นที่ราคารวมเท่ากับ K
6 100
20 50 30 10 40 6034 9
3 3 3 34ดูคำใบ้
N ≤ 5000 ลูปสามชั้นทำงานราว N3 / 6 ≈ 2 × 1010 ครั้ง ไม่ทัน แต่ O(N2) ยังทัน ให้ตรึงชิ้นกลาง j แล้วไล่ชิ้นท้าย k ที่อยู่หลัง j ราคาของชิ้นแรกต้องเป็น K − pj − pk พอดี ถ้ามีอาเรย์นับว่าราคาแต่ละค่าปรากฏกี่ครั้งในชิ้นที่อยู่ก่อน j ก็บวกได้ทันที ไล่ k ครบแล้วค่อยนับ pj ลงอาเรย์ ก่อนขยับ j ไปตัวถัดไป ราคาที่ต้องการอาจน้อยกว่า 1 หรือเกิน 1000000 และคำตอบเกิน int ได้
ดูเฉลย
#include <bits/stdc++.h>
using namespace std;
#define MAX_PRICE 1000000
int before[MAX_PRICE + 1];
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, k;
cin >> n >> k;
vector<int> price(n);
for (int i = 0; i < n; i++) {
cin >> price[i];
}
long long ways = 0;
for (int j = 0; j < n; j++) {
for (int l = j + 1; l < n; l++) {
int need = k - price[j] - price[l];
if (need >= 1 && need <= MAX_PRICE) {
ways += before[need];
}
}
before[price[j]]++;
}
cout << ways << '\n';
return 0;
}ข้อ 8 เพลย์ลิสต์ไม่ซ้ำ
แอปฟังเพลงเก็บประวัติเพลงที่น้องเปิดฟัง N เพลงตามลำดับ เพลงแต่ละเพลงมีรหัสเป็นจำนวนเต็ม เพลงเดียวกันอาจถูกเปิดหลายครั้ง แอปอยากทำสรุปท้ายปีว่าช่วงที่ฟังเพลงติดกันโดยไม่มีเพลงไหนซ้ำเลยยาวที่สุดกี่เพลง ถ้ามีหลายช่วงที่ยาวเท่ากัน ให้ตอบช่วงที่เริ่มเร็วที่สุด
ข้อมูลเข้า
บรรทัดแรกมีจำนวนเต็ม N โดยที่ 1 ≤ N ≤ 200000
บรรทัดที่สองมีรหัสเพลงที่เปิด ตั้งแต่เพลงที่ 1 ถึงเพลงที่ N คั่นด้วยช่องว่าง แต่ละค่าเป็นจำนวนเต็มตั้งแต่ 1 ถึง 1000000
ข้อมูลออก
บรรทัดเดียว มีความยาวของช่วงที่ยาวที่สุด ตามด้วยลำดับของเพลงแรกในช่วงนั้น (นับเพลงแรกของประวัติเป็นเพลงที่ 1) คั่นด้วยช่องว่าง
8
4 7 4 9 2 7 5 95 36
1 2 3 1 2 33 1ดูคำใบ้
ถ้าช่วงหนึ่งไม่มีเพลงซ้ำ ช่วงที่อยู่ข้างในก็ไม่มีเพลงซ้ำด้วย จึงใช้สองตัวชี้แบบหน้า 18.5 ได้ ขยาย r ทีละเพลง ใช้อาเรย์นับว่าในช่วงตอนนี้มีเพลงแต่ละรหัสกี่ครั้ง ถ้าเพลงใหม่ทำให้มีเพลงซ้ำ ให้เลื่อน l ไปพร้อมลดตัวนับจนไม่ซ้ำ ทั้ง l และ r เดินไปทางเดียวรวมไม่เกิน 2N ก้าว เป็น O(N) อัปเดตคำตอบเฉพาะตอนได้ช่วงที่ยาวกว่าเดิมจริงๆ จะได้ช่วงที่เริ่มเร็วที่สุดเอง
ดูเฉลย
#include <bits/stdc++.h>
using namespace std;
int inside[1000001];
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
vector<int> song(n);
for (int i = 0; i < n; i++) {
cin >> song[i];
}
int best = 0, best_start = 0;
int l = 0;
for (int r = 0; r < n; r++) {
inside[song[r]]++;
while (inside[song[r]] > 1) {
inside[song[l]]--;
l++;
}
if (r - l + 1 > best) {
best = r - l + 1;
best_start = l;
}
}
cout << best << ' ' << best_start + 1 << '\n';
return 0;
}