21.6 แบบฝึกหัด
เขียนแต่ละข้อเป็นไฟล์ใหม่ใน Code::Blocks รันแล้วเทียบกับผลลัพธ์ที่ต้องได้ให้ตรงทุกตัวอักษร รวมถึงช่องว่างและการขึ้นบรรทัด ติดจริงๆ ค่อยเปิดคำใบ้ และเปิดเฉลยหลังทำเสร็จแล้วเท่านั้น
ข้อ 1 ขนมสามชิ้น
ร้านสหกรณ์โรงเรียนมีขนม N ชิ้น ชิ้นที่ i ราคา pi บาท ทุกชิ้นต่างกันแม้ราคาจะเท่ากัน น้องมีเงิน M บาท อยากซื้อขนมให้เพื่อนสามคน คนละหนึ่งชิ้น จึงต้องเลือกขนมสามชิ้นที่ต่างกัน ราคารวมต้องไม่เกิน M และอยากใช้เงินให้คุ้มที่สุด คือราคารวมมากที่สุดเท่าที่ทำได้
ข้อมูลเข้า
บรรทัดแรกมีจำนวนเต็ม N และ M คั่นด้วยช่องว่าง โดยที่ 3 ≤ N ≤ 100 และ 1 ≤ M ≤ 3000000
บรรทัดที่สองมีจำนวนเต็ม p1 ถึง pN คั่นด้วยช่องว่าง โดยที่ 1 ≤ pi ≤ 1000000
ข้อมูลออก
ราคารวมที่มากที่สุดของขนมสามชิ้นที่ต่างกันซึ่งไม่เกิน M ถ้าเลือกสามชิ้นแบบไหนก็เกิน M ให้พิมพ์ -1
5 100
35 20 48 27 60954 10
5 4 6 8-1ดูคำใบ้
N ไม่เกิน 100 จำนวนแบบของการเลือกสามชิ้นคือ C(N, 3) ไม่ถึงสองแสน ลองทุกแบบได้สบายด้วยลูปซ้อนสามชั้นแบบหน้า 21.1 ให้ j เริ่มที่ i + 1 และ k เริ่มที่ j + 1 จะได้ชิ้นที่ต่างกันเสมอ ถ้าเริ่มทุกลูปที่ 0 จะหยิบชิ้นเดิมซ้ำได้ เก็บคำตอบเริ่มต้นเป็น -1 แล้วอัปเดตเมื่อเจอผลรวมที่ไม่เกินงบและมากกว่าเดิม
ดูเฉลย
#include <bits/stdc++.h>
using namespace std;
int main()
{
int n, budget;
cin >> n >> budget;
vector<int> price(n);
for (int i = 0; i < n; i++) {
cin >> price[i];
}
int best = -1;
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
for (int k = j + 1; k < n; k++) {
int total = price[i] + price[j] + price[k];
if (total <= budget && total > best) {
best = total;
}
}
}
}
cout << best << '\n';
return 0;
}ข้อ 2 สวิตช์ไฟหอประชุม
หอประชุมของโรงเรียนมีหลอดไฟ M ดวง หมายเลข 1 ถึง M และมีสวิตช์ N ตัว ช่างไฟต่อสายไว้แปลกๆ สวิตช์แต่ละตัวต่อกับหลอดหลายดวง กดหนึ่งครั้งแล้วทุกหลอดที่ต่ออยู่จะสลับสถานะ ดวงที่ดับจะติด ดวงที่ติดจะดับ ตอนนี้ไฟดับทุกดวง ภารโรงอยากเปิดไฟให้ติดครบทุกดวงโดยกดสวิตช์ให้น้อยครั้งที่สุด
ข้อมูลเข้า
บรรทัดแรกมีจำนวนเต็ม N และ M คั่นด้วยช่องว่าง โดยที่ 1 ≤ N ≤ 20 และ 1 ≤ M ≤ 20
อีก N บรรทัด บรรทัดที่ i บอกสวิตช์ตัวที่ i ขึ้นต้นด้วยจำนวนเต็ม k (1 ≤ k ≤ M) ตามด้วยหมายเลขหลอด k ตัวที่สวิตช์นี้ต่ออยู่ ไม่ซ้ำกัน แต่ละตัวอยู่ระหว่าง 1 ถึง M
ข้อมูลออก
จำนวนครั้งที่กดน้อยที่สุดที่ทำให้ไฟติดครบทุกดวง ถ้าทำไม่ได้ให้พิมพ์ -1
3 4
2 1 2
2 2 3
2 3 422 3
2 1 2
2 2 3-1ดูคำใบ้
กดสวิตช์เดิมสองครั้งเท่ากับไม่ได้กด และลำดับการกดไม่มีผล แต่ละสวิตช์จึงมีแค่สองทาง กดหรือไม่กด N ไม่เกิน 20 ลองทุกเซตย่อยด้วย bitmask แบบหน้า 21.2 ได้ เก็บสวิตช์แต่ละตัวเป็นตัวเลขที่ bit ที่ j - 1 เป็น 1 ถ้าสวิตช์นั้นสลับหลอดที่ j สถานะหลอดหลังกดคือ XOR (^ ในหน้า 12.3) ของทุกสวิตช์ที่เลือก ไม่ใช่ OR เพราะหลอดที่โดนสลับสองครั้งจะดับ เทียบกับ (1 << M) - 1 แล้วนับจำนวนสวิตช์ด้วย __builtin_popcount
ดูเฉลย
#include <bits/stdc++.h>
using namespace std;
int main()
{
int n, m;
cin >> n >> m;
vector<int> effect(n, 0);
for (int i = 0; i < n; i++) {
int k;
cin >> k;
for (int t = 0; t < k; t++) {
int lamp;
cin >> lamp;
effect[i] |= 1 << (lamp - 1);
}
}
int all_on = (1 << m) - 1;
int best = -1;
for (int mask = 0; mask < (1 << n); mask++) {
int lamps = 0;
for (int i = 0; i < n; i++) {
if ((mask >> i) & 1) {
lamps ^= effect[i];
}
}
if (lamps == all_on) {
int presses = __builtin_popcount(mask);
if (best == -1 || presses < best) {
best = presses;
}
}
}
cout << best << '\n';
return 0;
}ข้อ 3 ส่งพัสดุให้ครบทุกบ้าน
ร้านเครื่องเขียนรับส่งของถึงบ้าน วันนี้ต้องส่งพัสดุให้ N บ้าน หมายเลข 1 ถึง N ให้ร้านเป็นจุดหมายเลข 0 คนส่งของออกจากร้าน ขับไปส่งทุกบ้าน บ้านละหนึ่งครั้ง เรียงลำดับแบบไหนก็ได้ ส่งบ้านสุดท้ายเสร็จแล้วกลับบ้านตัวเองได้เลย ไม่ต้องกลับร้าน
ระยะทางจากจุด i ไปจุด j คือ d[i][j] กิโลเมตร ถนนบางเส้นเป็นทางเดินรถทางเดียว d[i][j] จึงอาจไม่เท่ากับ d[j][i] ให้หาระยะทางรวมที่สั้นที่สุด
ข้อมูลเข้า
บรรทัดแรกมีจำนวนเต็ม N โดยที่ 1 ≤ N ≤ 9
อีก N + 1 บรรทัด บรรทัดที่ i (นับจาก 0) มีจำนวนเต็ม N + 1 ตัว คือ d[i][0] ถึง d[i][N] คั่นด้วยช่องว่าง โดยที่ d[i][i] = 0 และ 1 ≤ d[i][j] ≤ 1000 เมื่อ i ≠ j
ข้อมูลออก
ระยะทางรวมที่สั้นที่สุด
3
0 4 9 7
4 0 3 8
9 3 0 2
7 8 2 092
0 5 1
5 0 10
1 1 02ดูคำใบ้
ลำดับการไปบ้านคือการเรียงสับเปลี่ยนของ 1 ถึง N มี N! แบบ 9! = 362880 แบบ แต่ละแบบบวกระยะ N ครั้ง ไม่ถึงสี่ล้านครั้ง ลองทุกลำดับด้วย next_permutation แบบหน้า 21.2 ได้ เริ่มจาก vector ที่เรียงแล้วคือ 1 2 ... N อย่าใส่ร้าน (จุด 0) ลงไปในลำดับ เพราะร้านต้องเป็นจุดเริ่มเสมอ ระยะของลำดับหนึ่งคือ d[0][ลำดับแรก] บวกระยะระหว่างบ้านที่ติดกันในลำดับ ระวังว่า d[i][j] กับ d[j][i] อาจไม่เท่ากันเพราะถนนเดินรถทางเดียว
ดูเฉลย
#include <bits/stdc++.h>
using namespace std;
int main()
{
int n;
cin >> n;
vector<vector<int>> dist(n + 1, vector<int>(n + 1));
for (int i = 0; i <= n; i++) {
for (int j = 0; j <= n; j++) {
cin >> dist[i][j];
}
}
vector<int> order(n);
for (int i = 0; i < n; i++) {
order[i] = i + 1;
}
int best = INT_MAX;
do {
int total = dist[0][order[0]];
for (int i = 0; i + 1 < n; i++) {
total += dist[order[i]][order[i + 1]];
}
best = min(best, total);
} while (next_permutation(order.begin(), order.end()));
cout << best << '\n';
return 0;
}ข้อ 4 ขนาดกล่องพัสดุ
บริษัทขนส่งจะผลิตกล่องพัสดุทรงสี่เหลี่ยมมุมฉากรุ่นใหม่ ที่จุของได้ V ลูกบาศก์เซนติเมตรพอดี ด้านกว้าง ยาว และสูงต้องเป็นจำนวนเต็มเซนติเมตร กล่องที่หมุนแล้วได้รูปเดิมนับเป็นขนาดเดียวกัน เช่น 1 × 2 × 3 กับ 3 × 1 × 2 จึงเขียนทุกขนาดเป็น a × b × c ที่ a ≤ b ≤ c ได้แบบเดียว
ฝ่ายผลิตอยากรู้ว่ามีกล่องที่จุ V ได้พอดีกี่ขนาด และขนาดที่ใช้กระดาษน้อยที่สุด ซึ่งก็คือขนาดที่พื้นที่ผิว 2(ab + bc + ca) น้อยที่สุด ใช้กระดาษกี่ตารางเซนติเมตร
ข้อมูลเข้า
บรรทัดเดียว มีจำนวนเต็ม V โดยที่ 1 ≤ V ≤ 1010
ข้อมูลออก
บรรทัดเดียว มีจำนวนเต็มสองตัวคั่นด้วยช่องว่าง คือจำนวนขนาดของกล่องที่จุ V ได้พอดี และพื้นที่ผิวที่น้อยที่สุดเป็นตารางเซนติเมตร
124 3271 30ดูคำใบ้
นับก่อนลอง วนกว้าง ยาว สูงครบสามชั้นถึง V ไม่มีทางทัน แต่พอเลือก a กับ b แล้ว c ถูกบังคับให้เป็น V / (a × b) ไม่ต้องวนหา แค่ตรวจว่าหารลงตัว แบบตัดลูปชั้นในในหน้า 21.1 ถึงอย่างนั้น ถ้าวน a ไปถึง V และวน b ไปจน a × b เกิน V ก็ยังต้องทำราว 1011 รอบเมื่อ V = 1010 ไม่ทัน ให้ใช้เงื่อนไข a ≤ b ≤ c จำกัดลูป a ≤ b ≤ c แปลว่า a × a × a ≤ V จึงวน a แค่ถึง 2154 และ a × b × b ≤ V จึงวน b แค่ถึงราว √(V / a) เหลือไม่ถึง 107 รอบ และได้ c ≥ b มาเองโดยไม่ต้องตรวจ V ใหญ่เกิน int และพื้นที่ผิวของกล่อง 1 × 1 × V ได้ถึงราว 4 × 1010 ใช้ long long ทุกตัว
ดูเฉลย
#include <bits/stdc++.h>
using namespace std;
int main()
{
long long volume;
cin >> volume;
long long sizes = 0;
long long best = LLONG_MAX;
for (long long a = 1; a * a * a <= volume; a++) {
for (long long b = a; a * b * b <= volume; b++) {
if (volume % (a * b) == 0) {
long long c = volume / (a * b);
sizes++;
best = min(best, 2 * (a * b + b * c + c * a));
}
}
}
cout << sizes << ' ' << best << '\n';
return 0;
}ข้อ 5 กระดานหมากรุกชำรุด
ชมรมหมากรุกมีกระดานขนาด N × N อยู่กระดานหนึ่ง ใช้มานานจนบางช่องแตก วางหมากไม่ได้ ประธานชมรมอยากรู้ว่าจะวางควีน N ตัวลงบนช่องที่ยังดีอยู่ โดยไม่มีควีนสองตัวไหนกินกันได้ ได้ทั้งหมดกี่แบบ ควีนกินกันได้เมื่ออยู่แถวเดียวกัน คอลัมน์เดียวกัน หรือแนวทแยงเดียวกัน ไม่ว่าจะห่างกันแค่ไหนและไม่ว่าระหว่างนั้นจะมีช่องที่แตกหรือไม่ ควีนทุกตัวเหมือนกัน สองแบบต่างกันถ้าชุดของช่องที่วางต่างกัน
ข้อมูลเข้า
บรรทัดแรกมีจำนวนเต็ม N โดยที่ 1 ≤ N ≤ 12
อีก N บรรทัด แต่ละบรรทัดเป็นข้อความยาว N ตัว ตัว . คือช่องที่ยังดี และ # คือช่องที่แตก
ข้อมูลออก
จำนวนแบบของการวางควีน N ตัว
4
....
....
....
....25
.....
.#...
.....
...#.
.....6ดูคำใบ้
วางทีละแถวแบบ N-Queens ในหน้า 21.3 ใช้อาเรย์จำคอลัมน์และแนวทแยงทั้งสองทิศที่ถูกใช้แล้ว เพิ่มเงื่อนไขเดียวคือช่อง # วางไม่ได้ ถ้าลองทุกการเรียงสับเปลี่ยนของคอลัมน์ด้วย next_permutation แล้วค่อยตรวจทีหลัง เมื่อ N = 12 ต้องตรวจ 12! ราว 4.8 × 108 แบบ ไม่ทันเวลา ต้องตัดตั้งแต่แถวแรกที่ขัดกฎ คำตอบที่ N = 12 ไม่เกิน 14200 จึงใช้ int ได้
ดูเฉลย
#include <bits/stdc++.h>
using namespace std;
int n;
vector<string> board;
bool col_used[12], diag1_used[23], diag2_used[23];
int ways = 0;
void place(int row)
{
if (row == n) {
ways++;
return;
}
for (int c = 0; c < n; c++) {
int d1 = row + c, d2 = row - c + n - 1;
if (board[row][c] == '#' || col_used[c] || diag1_used[d1] || diag2_used[d2]) {
continue;
}
col_used[c] = diag1_used[d1] = diag2_used[d2] = true;
place(row + 1);
col_used[c] = diag1_used[d1] = diag2_used[d2] = false;
}
}
int main()
{
cin >> n;
board.resize(n);
for (int i = 0; i < n; i++) {
cin >> board[i];
}
place(0);
cout << ways << '\n';
return 0;
}ข้อ 6 ซูโดกุหกช่อง
หนังสือพิมพ์ของโรงเรียนมีมุมปริศนาเป็นซูโดกุขนาด 6 × 6 ตารางแบ่งเป็นกล่องเล็ก 6 กล่อง แต่ละกล่องสูง 2 แถว กว้าง 3 คอลัมน์ ต้องเติมเลข 1 ถึง 6 ลงทุกช่อง ให้ทุกแถว ทุกคอลัมน์ และทุกกล่องมีเลข 1 ถึง 6 ครบ ตัวละหนึ่งครั้ง บางช่องมีเลขให้มาแล้ว
ปริศนาบางข้อมีหลายคำตอบ ให้พิมพ์คำตอบที่มาก่อนเมื่ออ่านเลขเรียงทีละแถวจากซ้ายไปขวา บนลงล่าง เทียบกันที่ช่องแรกที่ต่างกัน คำตอบที่เลขในช่องนั้นน้อยกว่ามาก่อน ถ้าเติมไม่ได้ หรือเลขที่ให้มาขัดกฎกันอยู่แล้ว ให้พิมพ์ NO
ข้อมูลเข้า
6 บรรทัด บรรทัดละ 6 จำนวน คั่นด้วยช่องว่าง แต่ละจำนวนอยู่ระหว่าง 0 ถึง 6 เลข 0 คือช่องว่าง
ข้อมูลออก
ตารางที่เติมเสร็จ 6 บรรทัด บรรทัดละ 6 จำนวน คั่นด้วยช่องว่างหนึ่งช่อง หรือ NO บรรทัดเดียว
0 0 3 0 1 0
5 6 0 3 2 0
0 5 4 2 0 3
2 0 6 4 5 0
0 1 2 0 4 5
0 4 0 1 0 04 2 3 5 1 6
5 6 1 3 2 4
1 5 4 2 6 3
2 3 6 4 5 1
3 1 2 6 4 5
6 4 5 1 3 20 2 3 4 5 6
0 0 0 0 0 0
1 0 0 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0NOดูคำใบ้
ใช้โครงซูโดกุ 4 × 4 ในหน้า 21.3 เติมช่องว่างทีละช่องจากซ้ายไปขวา บนลงล่าง ลองเลข 1 ถึง 6 จากน้อยไปมาก แล้วหยุดทันทีที่เติมครบ คำตอบแรกที่เจอจะเป็นแบบที่โจทย์ต้องการเอง กล่องสูง 2 แถว กว้าง 3 คอลัมน์ มุมซ้ายบนของกล่องคือแถว r / 2 * 2 คอลัมน์ c / 3 * 3 ระวังเลขที่ให้มาตั้งแต่ต้นอาจขัดกฎกันเอง ตัวค้นหาตรวจเฉพาะเลขที่เราเติม จึงต้องตรวจเลขที่ให้มาก่อนเริ่มค้น ทั้งแถว คอลัมน์ และกล่อง
ดูเฉลย
#include <bits/stdc++.h>
using namespace std;
int grid[6][6];
bool can_put(int r, int c, int d)
{
for (int k = 0; k < 6; k++) {
if ((k != c && grid[r][k] == d) || (k != r && grid[k][c] == d)) {
return false;
}
}
int top = r / 2 * 2, left = c / 3 * 3;
for (int i = top; i < top + 2; i++) {
for (int j = left; j < left + 3; j++) {
if ((i != r || j != c) && grid[i][j] == d) {
return false;
}
}
}
return true;
}
bool solve(int pos)
{
if (pos == 36) {
return true;
}
int r = pos / 6, c = pos % 6;
if (grid[r][c] != 0) {
return solve(pos + 1);
}
for (int d = 1; d <= 6; d++) {
if (can_put(r, c, d)) {
grid[r][c] = d;
if (solve(pos + 1)) {
return true;
}
grid[r][c] = 0;
}
}
return false;
}
int main()
{
for (int r = 0; r < 6; r++) {
for (int c = 0; c < 6; c++) {
cin >> grid[r][c];
}
}
bool ok = true;
for (int r = 0; r < 6; r++) {
for (int c = 0; c < 6; c++) {
if (grid[r][c] != 0 && !can_put(r, c, grid[r][c])) {
ok = false;
}
}
}
if (!ok || !solve(0)) {
cout << "NO\n";
return 0;
}
for (int r = 0; r < 6; r++) {
for (int c = 0; c < 6; c++) {
cout << grid[r][c] << (c < 5 ? ' ' : '\n');
}
}
return 0;
}ข้อ 7 เทน้ำสามขวด
ห้องแล็บเคมีมีขวดสามใบไม่มีขีดบอกปริมาตร ขวด A จุได้ A มิลลิลิตร ขวด B จุ B มิลลิลิตร และขวด C จุ C มิลลิลิตร ตอนเริ่มขวดทั้งสามมีน้ำ a b และ c มิลลิลิตรตามลำดับ ไม่มีก๊อกน้ำ และห้ามเทน้ำทิ้ง สิ่งที่ทำได้มีอย่างเดียว คือเทน้ำจากขวดหนึ่งไปอีกขวดหนึ่ง จนกว่าขวดต้นทางจะหมด หรือขวดปลายทางจะเต็ม แล้วแต่อย่างไหนเกิดก่อน
สถานะคือปริมาณน้ำในสามขวด ให้หาว่าเมื่อเทกี่ครั้งก็ได้ จะไปถึงสถานะที่ต่างกันได้กี่แบบ นับสถานะเริ่มต้นด้วย และขวด A มีน้ำได้กี่มิลลิลิตรบ้าง
ข้อมูลเข้า
บรรทัดแรกมีจำนวนเต็ม A B C คั่นด้วยช่องว่าง โดยที่ 1 ≤ A, B, C ≤ 100
บรรทัดที่สองมีจำนวนเต็ม a b c คั่นด้วยช่องว่าง โดยที่ 0 ≤ a ≤ A, 0 ≤ b ≤ B และ 0 ≤ c ≤ C
ข้อมูลออก
บรรทัดแรกพิมพ์จำนวนสถานะที่ไปถึงได้ บรรทัดที่สองพิมพ์ปริมาณน้ำที่เป็นไปได้ทั้งหมดของขวด A เรียงจากน้อยไปมาก ไม่ซ้ำ คั่นด้วยช่องว่างหนึ่งช่อง
3 5 8
0 0 816
0 1 2 32 4 6
0 1 411
0 1 2ดูคำใบ้
สถานะคือปริมาณน้ำในสามขวด น้ำไม่หายไปไหน ถ้ารู้ปริมาณในขวด A กับ B ก็รู้ขวด C เอง จึงใช้ visited[a][b] ขนาด 101 × 101 ตามแถวเทน้ำในตารางแทนสถานะแบบอื่นของหน้า 21.5 ส่วนการค้นเขียนแบบ DFS ในตัวอย่างข้ามแม่น้ำ ซึ่งใช้ visited[16] มิติเดียวเพราะสถานะเป็น bitmask จากสถานะหนึ่งลองเททั้ง 6 แบบ คือจากขวด x ไปขวด y ที่ x ≠ y ปริมาณที่เทคือค่าน้อยกว่าระหว่างน้ำในขวด x กับที่ว่างในขวด y แล้ว DFS ไปทุกสถานะใหม่ที่ยังไม่เคยไป นับจำนวนสถานะที่ไปถึง และจดว่าขวด A เคยมีน้ำเท่าไรบ้าง ลืมเช็ก visited แล้วจะเทกลับไปกลับมาไม่รู้จบจน stack เต็ม
ดูเฉลย
#include <bits/stdc++.h>
using namespace std;
int cap[3];
int total;
bool visited[101][101];
bool a_possible[101];
int states = 0;
void dfs(int a, int b)
{
visited[a][b] = true;
states++;
a_possible[a] = true;
int water[3] = {a, b, total - a - b};
for (int from = 0; from < 3; from++) {
for (int to = 0; to < 3; to++) {
if (from == to) {
continue;
}
int amount = min(water[from], cap[to] - water[to]);
int next[3] = {water[0], water[1], water[2]};
next[from] -= amount;
next[to] += amount;
if (!visited[next[0]][next[1]]) {
dfs(next[0], next[1]);
}
}
}
}
int main()
{
int a, b, c;
cin >> cap[0] >> cap[1] >> cap[2];
cin >> a >> b >> c;
total = a + b + c;
dfs(a, b);
cout << states << '\n';
bool first = true;
for (int x = 0; x <= cap[0]; x++) {
if (a_possible[x]) {
cout << (first ? "" : " ") << x;
first = false;
}
}
cout << '\n';
return 0;
}ข้อ 8 แบ่งขนมใส่ถุงให้หนักเท่ากัน
ร้านขนมได้รับขนมมา N ห่อ ห่อที่ i หนัก wi กรัม เจ้าของร้านอยากจัดกระเช้าของขวัญ K ถุง โดยต้องใช้ขนมครบทุกห่อ ห้ามแกะหรือแบ่งห่อ แต่ละห่ออยู่ในถุงเดียว และทุกถุงต้องหนักเท่ากันพอดี ให้ตอบว่าทำได้หรือไม่
ข้อมูลเข้า
บรรทัดแรกมีจำนวนเต็ม N และ K คั่นด้วยช่องว่าง โดยที่ 1 ≤ N ≤ 20 และ 2 ≤ K ≤ 5
บรรทัดที่สองมีจำนวนเต็ม w1 ถึง wN คั่นด้วยช่องว่าง โดยที่ 1 ≤ wi ≤ 1000000
ข้อมูลออก
YES ถ้าแบ่งได้ หรือ NO ถ้าแบ่งไม่ได้
6 3
5 4 3 3 2 1YES4 2
3 3 3 5NOดูคำใบ้
ทุกถุงต้องหนักเท่ากับผลรวมหาร K ถ้าหารไม่ลงตัว หรือห่อที่หนักที่สุดหนักเกินนั้น ตอบ NO ได้เลย ลองใส่ทุกห่อลงทุกถุงมี KN แบบ 520 เกินไปมาก แม้จะตัดเมื่อถุงหนักเกินก็ยังช้าเมื่อคำตอบเป็น NO ต้องตัดกิ่งเพิ่ม วิธีหนึ่งคือเติมทีละถุงให้ได้น้ำหนักพอดี เรียงห่อจากหนักไปเบา ถุงใหม่ทุกถุงเริ่มด้วยห่อที่หนักที่สุดที่ยังเหลือ เพราะห่อนั้นต้องอยู่ในถุงใดถุงหนึ่งอยู่แล้ว ถ้าเริ่มแบบนั้นแล้วไม่สำเร็จ ก็ไม่ต้องลองแบบอื่น ภายในถุงเดียวกันเลือกห่อตามลำดับจากหน้าไปหลังเหมือนเลือกเซตย่อย และถ้าห่อน้ำหนักเท่านี้เพิ่งลองแล้วไม่สำเร็จ ห่อถัดไปที่หนักเท่ากันก็ข้ามได้ เติมครบ K - 1 ถุงแล้ว ห่อที่เหลือหนักพอดีถุงสุดท้ายเอง
ดูเฉลย
#include <bits/stdc++.h>
using namespace std;
int n, k;
long long target;
vector<long long> weight;
vector<bool> used;
bool fill_bag(int bags_done, int start, long long load)
{
if (bags_done == k - 1) {
return true;
}
if (load == target) {
return fill_bag(bags_done + 1, 0, 0);
}
long long last_failed = -1;
for (int i = start; i < n; i++) {
if (used[i] || weight[i] == last_failed || load + weight[i] > target) {
continue;
}
used[i] = true;
if (fill_bag(bags_done, i + 1, load + weight[i])) {
return true;
}
used[i] = false;
last_failed = weight[i];
if (load == 0) {
break;
}
}
return false;
}
int main()
{
cin >> n >> k;
weight.resize(n);
long long total = 0;
for (int i = 0; i < n; i++) {
cin >> weight[i];
total += weight[i];
}
sort(weight.rbegin(), weight.rend());
used.assign(n, false);
target = total / k;
bool ok = total % k == 0 && weight[0] <= target && fill_bag(0, 0, 0);
cout << (ok ? "YES" : "NO") << '\n';
return 0;
}ข้อ 9 จัดเวรครู
สัปดาห์สอบมีเวรคุมสอบ N เวร และมีครู N คน ครูหนึ่งคนรับได้หนึ่งเวร และทุกเวรต้องมีครูรับหนึ่งคน ครูแต่ละคนเหนื่อยกับแต่ละเวรไม่เท่ากัน เพราะบ้านไกลใกล้และวิชาที่สอนต่างกัน ถ้าครูคนที่ i รับเวรที่ j จะเหนื่อย cij หน่วย ฝ่ายวิชาการอยากจัดเวรให้ความเหนื่อยรวมของครูทุกคนน้อยที่สุด
ข้อมูลเข้า
บรรทัดแรกมีจำนวนเต็ม N โดยที่ 1 ≤ N ≤ 13
อีก N บรรทัด บรรทัดที่ i มีจำนวนเต็ม ci1 ถึง ciN คั่นด้วยช่องว่าง โดยที่ 1 ≤ cij ≤ 1000000
ข้อมูลออก
ความเหนื่อยรวมที่น้อยที่สุด
3
4 2 8
4 3 7
3 1 6122
9 1
1 92ดูคำใบ้
ลองทุกการจับคู่คือ N! แบบ 13! เกินหกพันล้าน ไม่ทัน ใช้ branch and bound แบบหน้า 21.4 จัดเวรให้ครูทีละคน จำว่าเวรไหนมีคนรับแล้ว และเก็บผลรวมที่ดีที่สุดที่เจอ ก่อนลงไปต่อ ให้ประมาณแบบมองโลกในแง่ดีว่าครูที่เหลือแต่ละคนจะได้เวรที่ตัวเองเหนื่อยน้อยที่สุดในบรรดาเวรที่ยังว่าง (ไม่สนว่าจะชนกัน) ถ้าผลรวมตอนนี้บวกค่าประมาณนี้ไม่น้อยกว่าคำตอบที่ดีที่สุด ตัดกิ่งได้ อีกเรื่องที่ช่วยมากคือให้ครูแต่ละคนลองเวรที่เหนื่อยน้อยไปมาก จะได้คำตอบดีๆ เร็ว แล้วกิ่งที่เหลือถูกตัดง่ายขึ้น เรียงลำดับเวรของครูแต่ละคนไว้ก่อนครั้งเดียว
ดูเฉลย
#include <bits/stdc++.h>
using namespace std;
int n;
vector<vector<int>> cost;
vector<vector<int>> cheapest_first;
vector<bool> taken;
long long best = LLONG_MAX;
long long optimistic_rest(int teacher)
{
long long sum = 0;
for (int t = teacher; t < n; t++) {
for (int duty : cheapest_first[t]) {
if (!taken[duty]) {
sum += cost[t][duty];
break;
}
}
}
return sum;
}
void go(int teacher, long long total)
{
if (teacher == n) {
best = min(best, total);
return;
}
if (total + optimistic_rest(teacher) >= best) {
return;
}
for (int duty : cheapest_first[teacher]) {
if (!taken[duty]) {
taken[duty] = true;
go(teacher + 1, total + cost[teacher][duty]);
taken[duty] = false;
}
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
cost.assign(n, vector<int>(n));
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
cin >> cost[i][j];
}
}
cheapest_first.assign(n, vector<int>(n));
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
cheapest_first[i][j] = j;
}
sort(cheapest_first[i].begin(), cheapest_first[i].end(), [&](int a, int b) {
return cost[i][a] < cost[i][b];
});
}
taken.assign(n, false);
go(0, 0);
cout << best << '\n';
return 0;
}ข้อ 10 นับตะกร้าที่ไม่เกินงบ
ร้านเครื่องเขียนจัดโปรโมชันให้ลูกค้าเลือกของใส่ตะกร้าได้ตามใจ ร้านมีของ N ชิ้น ชิ้นที่ i ราคา pi บาท แต่ละชิ้นมีชิ้นเดียว น้องมีงบ S บาท อยากรู้ว่าเลือกของใส่ตะกร้าได้กี่แบบที่ราคารวมไม่เกินงบ ตะกร้าว่างที่ไม่เลือกอะไรเลยก็นับเป็นหนึ่งแบบ สองแบบต่างกันถ้ามีชิ้นใดชิ้นหนึ่งที่อยู่ในแบบหนึ่งแต่ไม่อยู่ในอีกแบบ แม้ราคาจะเท่ากัน
ข้อมูลเข้า
บรรทัดแรกมีจำนวนเต็ม N และ S คั่นด้วยช่องว่าง โดยที่ 1 ≤ N ≤ 40 และ 0 ≤ S ≤ 4 × 1010
บรรทัดที่สองมีจำนวนเต็ม p1 ถึง pN คั่นด้วยช่องว่าง โดยที่ 1 ≤ pi ≤ 109
ข้อมูลออก
จำนวนแบบของตะกร้าที่ราคารวมไม่เกิน S
4 10
3 5 7 2113 0
4 1 61ดูคำใบ้
N ถึง 40 ลองครบ 240 แบบไม่ทัน และตัดกิ่งก็ไม่ช่วย เพราะเมื่อ S ใหญ่ คำตอบเองก็ใหญ่ระดับ 1012 นับทีละแบบไม่ไหว ใช้ meet in the middle แบบหน้า 21.5 แบ่งของเป็นสองครึ่ง หาผลรวมทุกแบบของแต่ละครึ่ง ครึ่งละไม่เกิน 220 ค่า เรียงฝั่งขวา แล้วสำหรับผลรวม x ของฝั่งซ้ายแต่ละค่าที่ไม่เกิน S ให้นับว่าฝั่งขวามีกี่ค่าที่ไม่เกิน S - x ด้วย upper_bound ผลรวมกับคำตอบต้องเป็น long long ระวัง N เป็นเลขคี่ และอย่าลืมว่าตะกร้าว่างก็นับด้วย
ดูเฉลย
#include <bits/stdc++.h>
using namespace std;
vector<long long> all_sums(const vector<long long>& items)
{
int m = items.size();
vector<long long> sums(1 << m);
for (int mask = 0; mask < (1 << m); mask++) {
long long total = 0;
for (int i = 0; i < m; i++) {
if ((mask >> i) & 1) {
total += items[i];
}
}
sums[mask] = total;
}
return sums;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
long long budget;
cin >> n >> budget;
vector<long long> first_half, second_half;
for (int i = 0; i < n; i++) {
long long price;
cin >> price;
if (i < n / 2) {
first_half.push_back(price);
} else {
second_half.push_back(price);
}
}
vector<long long> left = all_sums(first_half);
vector<long long> right = all_sums(second_half);
sort(right.begin(), right.end());
long long ways = 0;
for (long long x : left) {
if (x <= budget) {
ways += upper_bound(right.begin(), right.end(), budget - x) - right.begin();
}
}
cout << ways << '\n';
return 0;
}