[0]zero-index clytie0x/zero-index

9.6 แบบฝึกหัด

เขียนแต่ละข้อเป็นไฟล์ใหม่ใน Code::Blocks รันแล้วเทียบกับผลลัพธ์ที่ต้องได้ให้ตรงทุกตัวอักษร รวมถึงช่องว่างและการขึ้นบรรทัด ติดจริงๆ ค่อยเปิดคำใบ้ และเปิดเฉลยหลังทำเสร็จแล้วเท่านั้น

ข้อ 1 ป้ายไฟเลขฐานสอง

ระดับง่าย เวลาไม่เกิน 1 วินาที

ป้ายหน้าห้องคอมพิวเตอร์แสดงตัวเลขด้วยหลอดไฟเรียงเป็นแถว หลอดที่ติดแทน 1 หลอดที่ดับแทน 0 อ่านเป็นเลขฐานสอง เช่น เลข 13 แสดงเป็น 1101 เพราะ 13 = 8 + 4 + 1 ให้แปลงจำนวนเต็ม N เป็นเลขฐานสองที่ป้ายต้องแสดง โดยไม่มีหลอดดับนำหน้า ยกเว้น N เป็น 0 ซึ่งป้ายแสดงหลอดดับหลอดเดียว

ข้อมูลเข้า

จำนวนเต็ม N หนึ่งตัว โดยที่ 0 ≤ N ≤ 1018

ข้อมูลออก

เลขฐานสองของ N ในบรรทัดเดียว ไม่มีเลข 0 นำหน้า ถ้า N เป็น 0 ให้พิมพ์ 0

13
1101
6
110
ดูคำใบ้

หลักขวาสุดของเลขฐานสองคือ n % 2 ส่วนหลักที่เหลือทางซ้ายทั้งหมดคือเลขฐานสองของ n / 2 ปัญหาคือหลักขวาสุดหาได้ก่อน แต่ต้องพิมพ์ทีหลัง ใช้วิธีพิมพ์ตอนขากลับแบบ dive ในหน้า 9.1 คือเรียกตัวเองกับ n / 2 ก่อน แล้วค่อยพิมพ์ n % 2 ระวังสองเรื่อง N ถึง 1018 ต้องใช้ long long และ N = 0 ต้องพิมพ์ 0 ไม่ใช่บรรทัดว่าง ลองให้กรณีฐานคือ n น้อยกว่า 2

ดูเฉลย
#include <stdio.h>

void print_binary(long long n)
{
    if (n >= 2) {
        print_binary(n / 2);
    }
    printf("%lld", n % 2);
}

int main()
{
    long long n;
    scanf("%lld", &n);
    print_binary(n);
    printf("\n");
    return 0;
}

ข้อ 2 ตัดริบบิ้น

ระดับง่าย เวลาไม่เกิน 1 วินาที

ร้านดอกไม้มีริบบิ้น N ม้วน ม้วนที่ i ยาว ai เซนติเมตร เจ้าของร้านอยากตัดริบบิ้นทุกม้วนเป็นชิ้นที่ยาวเท่ากันทุกชิ้น ความยาวของชิ้นเป็นจำนวนเต็มเซนติเมตร และต้องไม่เหลือเศษจากม้วนไหนเลย เพื่อให้ผูกช่อดอกไม้ได้สวย อยากได้ชิ้นที่ยาวที่สุดเท่าที่ทำได้ ให้หาว่าชิ้นยาวเท่าไร และได้ทั้งหมดกี่ชิ้น

ข้อมูลเข้า

บรรทัดแรกมีจำนวนเต็ม N โดยที่ 1 ≤ N ≤ 100000

บรรทัดที่สองมีจำนวนเต็ม a1 ถึง aN คั่นด้วยช่องว่าง โดยที่ 1 ≤ ai ≤ 109

ข้อมูลออก

บรรทัดแรกพิมพ์ความยาวของชิ้นที่ยาวที่สุด บรรทัดที่สองพิมพ์จำนวนชิ้นทั้งหมดที่ตัดได้

3
84 60 36
12
15
2
7 5
1
12
ดูคำใบ้

ความยาวของชิ้นต้องหารความยาวของทุกม้วนลงตัว และยาวที่สุด นั่นคือ ห.ร.ม. ของทุกม้วน ใช้ gcd แบบยุคลิดในหน้า 9.2 กับสองม้วนแรก แล้วเอาผลไปหา ห.ร.ม. กับม้วนที่สาม ไล่ไปเรื่อยๆ ด้วยลูป อย่าเขียนการไล่ทั้งอาเรย์เป็น recursion เพราะ N ถึงแสนจะลึกเกินที่ stack รับได้ตามหน้า 9.4 จำนวนชิ้นรวมใหญ่ได้ถึง 1014 ต้องใช้ long long ส่วนวิธีไล่ลองทุกความยาวจากม้วนที่สั้นที่สุดลงมา ต้องลองเกือบพันล้านความยาวเมื่อทุกม้วนยาวเกือบพันล้าน ไม่ทันเวลา

ดูเฉลย
#include <stdio.h>

int lengths[100000];

int gcd(int a, int b)
{
    if (b == 0) {
        return a;
    }
    return gcd(b, a % b);
}

int main()
{
    int n;
    scanf("%d", &n);
    for (int i = 0; i < n; i++) {
        scanf("%d", &lengths[i]);
    }
    int piece = lengths[0];
    for (int i = 1; i < n; i++) {
        piece = gcd(piece, lengths[i]);
    }
    long long pieces = 0;
    for (int i = 0; i < n; i++) {
        pieces += lengths[i] / piece;
    }
    printf("%d\n", piece);
    printf("%lld\n", pieces);
    return 0;
}

ข้อ 3 ขีดบนไม้บรรทัด

ระดับง่าย เวลาไม่เกิน 1 วินาที

ขีดบนไม้บรรทัดยาวไม่เท่ากัน ขีดตรงกลางยาวที่สุด ครึ่งซ้ายกับครึ่งขวาก็มีขีดกลางของตัวเองที่สั้นลงมาหนึ่งระดับ แบ่งครึ่งต่อไปเรื่อยๆ แบบนี้ น้องอยากพิมพ์ลายขีดของไม้บรรทัดลงหน้าจอ โดยวางไม้บรรทัดในแนวตั้ง ขีดหนึ่งขีดคือหนึ่งบรรทัด ความยาวของขีดคือจำนวนตัว - ในบรรทัดนั้น

ไม้บรรทัดระดับ K นิยามแบบนี้

  • ไม้บรรทัดระดับ 1 คือขีดยาว 1 หนึ่งขีด
  • ไม้บรรทัดระดับ K เมื่อ K ≥ 2 คือไม้บรรทัดระดับ K - 1 ตามด้วยขีดยาว K หนึ่งขีด แล้วตามด้วยไม้บรรทัดระดับ K - 1 อีกชุด

เช่น ไม้บรรทัดระดับ 2 มีสามขีด ยาว 1 2 1 ตามลำดับ ให้พิมพ์ไม้บรรทัดระดับ K

ข้อมูลเข้า

จำนวนเต็ม K หนึ่งตัว โดยที่ 1 ≤ K ≤ 15

ข้อมูลออก

ลายขีดของไม้บรรทัดระดับ K บรรทัดละหนึ่งขีด แต่ละบรรทัดมีแต่ตัว - ตามความยาวของขีด ไม่มีช่องว่าง

3
-
--
-
---
-
--
-
ดูคำใบ้

นิยามในโจทย์เป็น recursion อยู่แล้ว แปลงเป็นฟังก์ชัน ruler(k) ที่เรียก ruler(k - 1) พิมพ์ขีด k ตัว แล้วเรียก ruler(k - 1) อีกครั้ง ถ้าให้กรณีฐานเป็น k = 0 ที่ไม่พิมพ์อะไรเลย ไม่ต้องเขียนระดับ 1 แยก เพราะระดับ 1 จะกลายเป็นว่างหนึ่งรอบ ขีดหนึ่งตัว ว่างอีกหนึ่งรอบ ลองเทียบกับโครงของหอคอยฮานอยในหน้า 9.5

ดูเฉลย
#include <stdio.h>

void ruler(int k)
{
    if (k == 0) {
        return;
    }
    ruler(k - 1);
    for (int i = 0; i < k; i++) {
        printf("-");
    }
    printf("\n");
    ruler(k - 1);
}

int main()
{
    int k;
    scanf("%d", &k);
    ruler(k);
    return 0;
}

ข้อ 4 ค้นรายชื่อผู้เข้าสอบ

ระดับกลาง เวลาไม่เกิน 1 วินาที

หน้าสนามสอบติดรายชื่อผู้มีสิทธิ์สอบ N คน เรียงตามเลขประจำตัวสอบจากน้อยไปมาก เลขประจำตัวไม่ซ้ำกัน มีผู้ปกครอง Q คนมาถามเจ้าหน้าที่ แต่ละคนบอกเลขประจำตัวของลูกหนึ่งเลข เจ้าหน้าที่ต้องตอบว่าเลขนั้นอยู่ลำดับที่เท่าไรในรายชื่อ โดยคนแรกในรายชื่อคือลำดับที่ 1 ถ้าไม่มีเลขนั้นในรายชื่อให้ตอบ 0

ข้อมูลเข้า

บรรทัดแรกมีจำนวนเต็ม N และ Q คั่นด้วยช่องว่าง โดยที่ 1 ≤ N ≤ 100000 และ 1 ≤ Q ≤ 100000

บรรทัดที่สองมีเลขประจำตัวสอบ N ตัว เรียงจากน้อยไปมาก ไม่ซ้ำกัน แต่ละตัวอยู่ระหว่าง 1 ถึง 109

อีก Q บรรทัด แต่ละบรรทัดมีเลขที่ถูกถามหนึ่งตัว อยู่ระหว่าง 1 ถึง 109

ข้อมูลออก

Q บรรทัด ตอบคำถามตามลำดับ บรรทัดละหนึ่งข้อ เป็นลำดับในรายชื่อ หรือ 0 ถ้าไม่มี

10 6
1012 1047 1105 1188 1230 1302 1377 1419 1460 1525
1460
1200
1012
1525
1000
2000
9
0
1
10
0
0
ดูคำใบ้

รายชื่อเรียงแล้ว ใช้ binary search แบบ recursive ในหน้า 9.4 ได้ทั้งฟังก์ชัน ฟังก์ชันคืน index ที่เริ่มจาก 0 แต่โจทย์ถามลำดับที่เริ่มจาก 1 จึงต้องบวกหนึ่ง ส่วนกรณีไม่เจอให้พิมพ์ 0 ทดสอบเลขตัวแรก ตัวสุดท้าย เลขที่น้อยกว่าทุกตัว และเลขที่มากกว่าทุกตัวให้ครบ วิธีไล่ดูทีละคนสำหรับทุกคำถาม ต้องเทียบถึง 1010 ครั้งเมื่อ N กับ Q เป็นแสน ไม่ทันเวลา

ดูเฉลย
#include <stdio.h>

int ids[100000];

int search(int a[], int low, int high, int target)
{
    if (low > high) {
        return -1;
    }
    int mid = (low + high) / 2;
    if (a[mid] == target) {
        return mid;
    }
    if (a[mid] < target) {
        return search(a, mid + 1, high, target);
    }
    return search(a, low, mid - 1, target);
}

int main()
{
    int n, q;
    scanf("%d %d", &n, &q);
    for (int i = 0; i < n; i++) {
        scanf("%d", &ids[i]);
    }
    for (int k = 0; k < q; k++) {
        int target;
        scanf("%d", &target);
        printf("%d\n", search(ids, 0, n - 1, target) + 1);
    }
    return 0;
}

ข้อ 5 นั่งเว้นที่

ระดับกลาง เวลาไม่เกิน 1 วินาที

ห้องสอบมีที่นั่งเรียงเป็นแถวเดียว n ที่ กติกาของห้องสอบคือห้ามมีนักเรียนสองคนนั่งติดกัน จะมีคนนั่งกี่คนก็ได้ รวมถึงไม่มีใครนั่งเลย ครูอยากเห็นทุกแบบที่เป็นไปได้ ให้เขียนแต่ละแบบเป็นสตริงยาว n ตัว ตัวที่ i แทนที่นั่งที่ i โดย 1 คือมีคนนั่ง และ 0 คือว่าง

ข้อมูลเข้า

จำนวนเต็ม n หนึ่งตัว โดยที่ 1 ≤ n ≤ 20

ข้อมูลออก

พิมพ์ทุกแบบที่ไม่มี 1 สองตัวติดกัน บรรทัดละหนึ่งแบบ เรียงแบบพจนานุกรมโดยถือว่า 0 มาก่อน 1 บรรทัดสุดท้ายพิมพ์จำนวนแบบทั้งหมด

3
000
001
010
100
101
5
ดูคำใบ้

ใช้โครงพิมพ์สตริงฐานสองทุกแบบในหน้า 9.5 เติมทีละที่นั่ง ลอง '0' ก่อนแล้วค่อยลอง '1' ผลจะออกมาเรียงตามโจทย์เอง สิ่งที่เพิ่มคือก่อนใส่ '1' ที่ตำแหน่ง pos ให้เช็กว่าที่นั่ง pos - 1 ไม่ใช่ '1' ระวังตำแหน่ง 0 ที่ไม่มีที่นั่งทางซ้าย ส่วนจำนวนแบบให้นับด้วยตัวแปร global ทุกครั้งที่ถึงกรณีฐาน

ดูเฉลย
#include <stdio.h>

int n;
int total = 0;
char seats[22];

void place(int pos)
{
    if (pos == n) {
        seats[n] = '\0';
        printf("%s\n", seats);
        total++;
        return;
    }
    seats[pos] = '0';
    place(pos + 1);
    if (pos == 0 || seats[pos - 1] == '0') {
        seats[pos] = '1';
        place(pos + 1);
    }
}

int main()
{
    scanf("%d", &n);
    place(0);
    printf("%d\n", total);
    return 0;
}

ข้อ 6 จับฉลากของขวัญ

ระดับกลาง เวลาไม่เกิน 1 วินาที

งานปีใหม่ของห้อง เพื่อน n คนหมายเลข 1 ถึง n ต่างคนต่างเอาของขวัญมาคนละหนึ่งชิ้น แล้วจับฉลากแลกกัน ทุกคนได้ของขวัญคนละหนึ่งชิ้น ของขวัญทุกชิ้นมีคนได้ และไม่มีใครได้ของขวัญที่ตัวเองเอามา ครูอยากเห็นผลจับฉลากทุกแบบที่เป็นไปได้

ผลจับฉลากหนึ่งแบบเขียนเป็นตัวเลข n ตัว g1 g2 ... gn โดย gi คือหมายเลขของเพื่อนที่เอาของขวัญชิ้นที่คนที่ i ได้มา

ข้อมูลเข้า

จำนวนเต็ม n หนึ่งตัว โดยที่ 1 ≤ n ≤ 8

ข้อมูลออก

ผลจับฉลากทุกแบบ บรรทัดละหนึ่งแบบ ในบรรทัดพิมพ์ g1 ถึง gn คั่นด้วยช่องว่างหนึ่งช่อง เรียงแบบพจนานุกรม คือเทียบตัวแรกก่อน ถ้าเท่ากันค่อยเทียบตัวถัดไป แบบที่น้อยกว่าพิมพ์ก่อน ถ้าไม่มีผลจับฉลากที่เป็นไปได้เลย ให้พิมพ์ none

3
2 3 1
3 1 2
ดูคำใบ้

ใช้โครงการเรียงสับเปลี่ยนในหน้า 9.5 ทั้งหมด ตำแหน่ง pos คือคนที่ pos + 1 และค่า v ที่เลือกคือเจ้าของของขวัญที่เขาได้ เพิ่มเงื่อนไขเดียว คือห้ามเลือก v ที่เท่ากับ pos + 1 ลูปลอง v จากน้อยไปมาก ผลจะเรียงตามโจทย์เอง นับจำนวนแบบที่พิมพ์ไว้ด้วย ถ้าจบแล้วยังเป็น 0 ค่อยพิมพ์ none ลองคิดว่า n = 1 ได้กี่แบบ

ดูเฉลย
#include <stdio.h>

int n;
int total = 0;
int gift[10];
int taken[10];

void draw(int pos)
{
    if (pos == n) {
        for (int i = 0; i < n; i++) {
            if (i > 0) {
                printf(" ");
            }
            printf("%d", gift[i]);
        }
        printf("\n");
        total++;
        return;
    }
    for (int v = 1; v <= n; v++) {
        if (taken[v] == 0 && v != pos + 1) {
            taken[v] = 1;
            gift[pos] = v;
            draw(pos + 1);
            taken[v] = 0;
        }
    }
}

int main()
{
    scanf("%d", &n);
    draw(0);
    if (total == 0) {
        printf("none\n");
    }
    return 0;
}

ข้อ 7 บันไดชำรุด

ระดับยาก เวลาไม่เกิน 1 วินาที

บันไดขึ้นอาคารเรียนมี n ขั้น หมายเลข 1 ถึง n น้องยืนอยู่ที่พื้นซึ่งนับเป็นขั้น 0 ก้าวหนึ่งครั้งขึ้นได้ 1 ขั้น 2 ขั้น หรือ 3 ขั้น บันไดเก่าแล้ว มีบางขั้นชำรุดซึ่งห้ามเหยียบ แต่ก้าวข้ามได้ ให้นับว่ามีกี่วิธีที่จะขึ้นไปหยุดที่ขั้น n พอดี สองวิธีถือว่าต่างกันถ้าลำดับของขั้นที่เหยียบต่างกัน

เช่น บันได 4 ขั้นที่ไม่มีขั้นชำรุดเลย มี 7 วิธี คือก้าว 1+1+1+1, 1+1+2, 1+2+1, 2+1+1, 2+2, 1+3 และ 3+1

ข้อมูลเข้า

บรรทัดแรกมีจำนวนเต็ม n และ k คั่นด้วยช่องว่าง คือจำนวนขั้นและจำนวนขั้นที่ชำรุด โดยที่ 1 ≤ n ≤ 70 และ 0 ≤ k ≤ n - 1

ถ้า k มากกว่า 0 บรรทัดที่สองมีหมายเลขขั้นที่ชำรุด k ตัว ไม่ซ้ำกัน คั่นด้วยช่องว่าง แต่ละตัวอยู่ระหว่าง 1 ถึง n - 1 ขั้น n ไม่ชำรุดเสมอ ถ้า k เป็น 0 จะไม่มีบรรทัดนี้

ข้อมูลออก

จำนวนวิธีขึ้นไปถึงขั้น n รับประกันว่าคำตอบไม่เกิน 3 × 1018

4 0
7
6 1
3
8
ดูคำใบ้

ให้ ways(i) คือจำนวนวิธีเดินจากพื้นไปถึงขั้น i พอดี ก้าวสุดท้ายมาจากขั้น i - 1 i - 2 หรือ i - 3 จึงได้ ways(i - 1) + ways(i - 2) + ways(i - 3) กรณีฐานคือ i ติดลบได้ 0 วิธี ขั้นที่ชำรุดได้ 0 วิธี และพื้นคือขั้น 0 ได้ 1 วิธี เขียนตรงๆ จะเรียกตัวเองซ้ำแบบฟีโบนัชชีในหน้า 9.3 และช้าเกินเมื่อ n ถึง 70 ต้องจดคำตอบลงอาเรย์ long long ระวังให้ดี คำตอบของขั้นหนึ่งเป็น 0 ได้บ่อย ถ้าใช้ 0 แทนคำว่ายังไม่ได้คำนวณ ขั้นที่คำตอบเป็น 0 จะถูกคำนวณใหม่ทุกครั้ง ใช้ -1 แบบในหน้า 9.3

ดูเฉลย
#include <stdio.h>

int broken[71];
long long memo[71];

long long ways(int step)
{
    if (step < 0) {
        return 0;
    }
    if (broken[step] == 1) {
        return 0;
    }
    if (step == 0) {
        return 1;
    }
    if (memo[step] != -1) {
        return memo[step];
    }
    memo[step] = ways(step - 1) + ways(step - 2) + ways(step - 3);
    return memo[step];
}

int main()
{
    int n, k;
    scanf("%d %d", &n, &k);
    for (int i = 0; i < k; i++) {
        int step;
        scanf("%d", &step);
        broken[step] = 1;
    }
    for (int i = 0; i <= n; i++) {
        memo[i] = -1;
    }
    printf("%lld\n", ways(n));
    return 0;
}

ข้อ 8 ฮานอยครั้งที่ K

ระดับยาก เวลาไม่เกิน 1 วินาที

พิพิธภัณฑ์วิทยาศาสตร์มีหุ่นยนต์ย้ายหอคอยฮานอยโชว์ทั้งวัน หอคอยมีแผ่นกลม n แผ่น หมายเลข 1 ถึง n จากเล็กไปใหญ่ เริ่มต้นทุกแผ่นอยู่ที่หลัก A หุ่นยนต์ย้ายทั้งหมดไปหลัก C ตามขั้นตอนนี้ทุกประการ

ย้าย m แผ่นจากหลัก X ไปหลัก Y โดยมีหลัก Z เป็นที่พัก ทำดังนี้

  1. ถ้า m เป็น 0 ไม่ต้องทำอะไร
  2. ย้าย m - 1 แผ่นจากหลัก X ไปหลัก Z โดยมีหลัก Y เป็นที่พัก
  3. ย้ายแผ่นหมายเลข m จากหลัก X ไปหลัก Y หนึ่งครั้ง
  4. ย้าย m - 1 แผ่นจากหลัก Z ไปหลัก Y โดยมีหลัก X เป็นที่พัก

หุ่นยนต์เริ่มด้วยการย้าย n แผ่นจากหลัก A ไปหลัก C โดยมีหลัก B เป็นที่พัก ซึ่งเป็นขั้นตอนเดียวกับโปรแกรมหอคอยฮานอยในหน้า 9.5 ผู้ชมถามคำถาม Q ข้อ แต่ละข้อถามว่าการย้ายครั้งที่ K หุ่นยนต์ย้ายแผ่นหมายเลขอะไร จากหลักไหนไปหลักไหน

ข้อมูลเข้า

บรรทัดแรกมีจำนวนเต็ม n และ Q คั่นด้วยช่องว่าง โดยที่ 1 ≤ n ≤ 60 และ 1 ≤ Q ≤ 1000

อีก Q บรรทัด แต่ละบรรทัดมีจำนวนเต็ม K หนึ่งตัว โดยที่ 1 ≤ K ≤ 2n - 1

ข้อมูลออก

Q บรรทัด ตอบคำถามตามลำดับ แต่ละบรรทัดมีหมายเลขแผ่น หลักต้นทาง และหลักปลายทางของการย้ายครั้งที่ K คั่นด้วยช่องว่าง หลักเขียนเป็นตัวอักษร A B หรือ C

3 7
1
2
3
4
5
6
7
1 A C
2 A B
1 C B
3 A C
1 B A
2 B C
1 A C
ดูคำใบ้

ไล่ย้ายจริงไปจนถึงครั้งที่ K ไม่ทัน เพราะ K ถึงราว 1018 ให้ใช้โครงของวิธีย้ายแทน การย้าย n แผ่นมีสามช่วง ช่วงแรกคือย้าย n - 1 แผ่นซึ่งใช้ 2n-1 - 1 ครั้ง ครั้งถัดมาคือครั้งที่ 2n-1 ซึ่งย้ายแผ่นที่ n และช่วงสุดท้ายคือย้าย n - 1 แผ่นอีกรอบ ดูว่า K ตกช่วงไหน ถ้าตกช่วงแรกหรือช่วงสุดท้าย ปัญหาเล็กลงเหลือ n - 1 แผ่น โดยหลักสลับบทบาทแบบในหน้า 9.5 และช่วงสุดท้ายต้องลบ 2n-1 ออกจาก K ก่อน เก็บ 2i ไว้ในอาเรย์ long long ด้วยลูปคูณสองตั้งแต่ต้น แล้วรับ K ด้วย %lld

ดูเฉลย
#include <stdio.h>

long long power_of_two[61];

void find_move(int n, long long k, char from, char to, char via)
{
    long long middle = power_of_two[n - 1];
    if (k < middle) {
        find_move(n - 1, k, from, via, to);
    } else if (k == middle) {
        printf("%d %c %c\n", n, from, to);
    } else {
        find_move(n - 1, k - middle, via, to, from);
    }
}

int main()
{
    power_of_two[0] = 1;
    for (int i = 1; i <= 60; i++) {
        power_of_two[i] = power_of_two[i - 1] * 2;
    }
    int n, q;
    scanf("%d %d", &n, &q);
    for (int i = 0; i < q; i++) {
        long long k;
        scanf("%lld", &k);
        find_move(n, k, 'A', 'C', 'B');
    }
    return 0;
}