Tuyển sinh 🎓 Mình nhận đào tạo lập trình giải thuật thi đấu: thi vào lớp 10 chuyên tin · thi học sinh giỏi tin học cấp tỉnh/thành · thi vào đội tuyển quốc gia · Olympic tin học sinh viên / ICPC · phỏng vấn thuật toán. Bấm vào đây để xem thông tin khóa học → Xem khóa học

Chia Quà 4

Chia Quà 4
Nguồn: Codeforces Group Độ khó: Dễ Toán học Chẵn lẻ

Đề bài

Nhân dịp đi du lịch, Nam đã mua $n$ quả táo về làm quà cho hai người bạn thân là Tuấn và Sơn. Mỗi quả táo có trọng lượng là 100g hoặc 200g. Bây giờ Nam muốn chia đều $n$ quả táo trên thành hai phần có trọng lượng bằng nhau để chia cho bạn. Khi mua thì Nam quên kiểm tra điều này. Bạn hãy giúp Nam xem thử có thể chia $n$ quả táo trên thành hai phần có trọng lượng bằng nhau được không.

Dữ liệu vào:

  • Dòng thứ nhất là số nguyên $n$ (số lượng các quả táo), dữ liệu vào đảm bảo $1 \le n \le 10^6$.
  • Dòng thứ hai gồm $n$ số nguyên $a_1, a_2, \ldots, a_n$ là trọng lượng các quả táo mà Nam đã mua, dữ liệu vào đảm bảo chỉ có hai trường hợp $a_i = 100$ hoặc $a_i = 200$.

Dữ liệu ra: nếu Nam có thể chia được thì in là YES, nếu không chia được thì in là NO.

Subtask:

  • 30% số test đầu tiên có $n \le 400$.
  • 30% số test tiếp theo có $n \le 10^4$.
  • 20% số test tiếp theo có $n \le 10^5$.
  • 20% số test còn lại có $n \le 10^6$.
Ví dụ 1 – input
3
100 200 100
Ví dụ 1 – output
YES
Ví dụ 2 – input
4
100 100 100 200
Ví dụ 2 – output
NO
Ví dụ 3 – input
4
100 200 200 100
Ví dụ 3 – output
YES

Giải thích:

  • Test 1: Phần một gồm quả táo thứ 1 và quả táo thứ 3, phần 2 là quả táo thứ 2 (mỗi phần 200g).
  • Test 3: Phần một gồm quả táo thứ 1 và quả táo thứ 2, phần 2 là quả táo thứ 3 và thứ 4 (mỗi phần 300g).

Lời mở đầu

Bài này mình được một bạn học viên hỏi nên sẵn tiện mình viết bài giải chi tiết để nhiều bạn được tham khảo luôn. Hy vọng mang đến được giá trị cho các bạn 😇. Bạn nào muốn thử nộp bài để hệ thống chấm xem có đúng hay không thì link bài đây nhé:

Dành cho học viên · Ưu tiênNộp bài trên hệ thống CLUE OJ

Link này chỉ học viên đã tham gia tổ chức mới vào được. Học viên nên ưu tiên nộp ở đây vì sẽ được chấm nhanh hơn.

Nộp bài trên CLUE OJ
Dành cho tất cả mọi ngườiNộp bài trên Codeforces

Bạn nào muốn thử nộp bài để hệ thống chấm xem có đúng hay không thì dùng link này nhé. Chưa có tài khoản thì tạo tài khoản rồi đăng nhập nộp bài thử hen.

Nộp bài trên Codeforces

Thì thực ra hỏi bài này dùng giải thuật gì thì mình cũng không biết phải trả lời thế nào luôn 😅 vì nó chỉ là một tư duy toán học nhìn ra được thôi, đặc biệt là nó rất thực tế. Tuy nhiên tưởng chừng đơn giản như vậy nhưng bạn nào không nhìn ra được vấn đề là sẽ phải đi xử lý rườm rà phức tạp cho bài này lắm.

Lộ trình lời giải, bấm vào từng phần để nhảy tới:

Ý tưởngLiên hệ thực tế: chia tiền 1000 và 2000

Thì hãy liên hệ thực tế nhé: quả táo trọng lượng 100g thì hãy tương tự nó là tờ 1000 VNĐ, quả táo trọng lượng 200g thì hãy tương tự nó là tờ 2000 VNĐ. Nghĩa là số lượng quả táo 100g tương tự là số tờ tiền 1000 VNĐ và số lượng quả táo 200g tương tự là số tờ tiền 2000 VNĐ. Mình lấy mệnh giá tiền Việt Nam để các bạn dễ quen thuộc.

Ví dụ ta đang có $x_1$ tờ 1000 VNĐ và $x_2$ tờ 2000 VNĐ thì cần chia ra cho 2 người làm sao để số tiền mỗi người có được là bằng nhau? Tất nhiên chỉ được chia trong phạm vi 2 loại tờ tiền 1000 VNĐ, 2000 VNĐ đang có chứ không cho đổi sang tờ tiền mệnh giá khác nhé. Hãy tìm cách xem liệu có thể chia cho 2 người bằng nhau được không?

Trường hợp 1: cả hai đều chẵn

Ta sẽ thấy một điều rất dễ để nhìn ra: nếu số lượng 2 loại tờ tiền 1000 VNĐ, 2000 VNĐ đều là số chẵn (tức là $x_1$ và $x_2$ đều chẵn) thì chắc chắn luôn sẽ chia được, cụ thể mỗi người sẽ nhận được $\frac{x_1}{2}$ tờ 1000 VNĐ và $\frac{x_2}{2}$ tờ 2000 VNĐ.

Nếu không được như vậy, tức là $x_1$ hay $x_2$ có cái là số lẻ (hay cả 2 đều lẻ) thì lúc này sẽ phải xem xét có chia được hay không. Nếu có số lượng nào là số lẻ, tức là nếu chia số lượng đó đều ra cho 2 người thì sẽ bị dư ra 1 tờ mệnh giá đó, bởi nguyên tắc số lẻ thì % 2 (phép chia dư cho 2) sẽ luôn ra kết quả là 1. Ví dụ đang có 3 tờ thì chia ra mỗi người 1 tờ và dư 1 tờ; đang có 1 tờ thì mỗi người 0 tờ và dư 1 tờ. Vấn đề là liệu 1 tờ dư ra đó ta có thể chia làm sao để đều cho mỗi người một nửa không?

Trường hợp 2: tờ dư ra là tờ 2000 VNĐ

Nếu tờ tiền đang dư ra có mệnh giá 2000 VNĐ, nghĩa là mỗi người phải được một nửa, tức 1000 VNĐ. Vậy thì một cách chia rất thực tế dễ nhìn ra: một trong 2 người, ví dụ người A, sẽ chủ động đưa 1000 VNĐ cho người B, rồi người A nhận về tờ 2000 VNĐ đó. Như thế là mỗi người được thêm 1000 VNĐ thôi.

Người A có tờ 1000 Người B được 1000 2000 tờ dư → A 1000 A thối 1000 cho B → mỗi người thêm đúng 1000

Vấn đề là muốn được như vậy thì 1 trong 2 người phải đang có tờ 1000 VNĐ thì mới thối lại được cho người kia. Còn nếu không có tờ 1000 VNĐ nào thì coi như không có cách nào chia được.

Trường hợp 3: tờ dư ra là tờ 1000 VNĐ

Còn nếu tờ tiền bị dư ra mà có mệnh giá là 1000 VNĐ thì thôi xác định sẽ chẳng có cách chia nào được, bởi lúc này mỗi người sẽ phải nhận 500 VNĐ, tức là người này phải đưa 500 VNĐ cho người kia rồi mới nhận về được tờ 1000 VNĐ. Nhưng mà ở đây ta chỉ có 2 loại tiền là 1000 VNĐ và 2000 VNĐ chứ làm gì có mệnh giá 500 VNĐ nên sẽ chẳng thể thối lại được.

Nhìn theo tổng trọng lượng
Tổng trọng lượng là $100x_1 + 200x_2$. Mỗi phần phải nặng $50x_1 + 100x_2$ gam. Nếu $x_1$ lẻ thì con số này không chia hết cho 100, mà mọi phần ghép từ các quả 100g, 200g đều là bội của 100 → chắc chắn NO. Đây chính là "tờ 500 VNĐ không tồn tại" ở trên.

Tóm lạiGiải thuật chỉ là vài câu if

Vậy tóm lại giải thuật lúc này là gì? Gọi $x_1$ là số lượng quả táo có trọng lượng 100g, $x_2$ là số lượng quả táo có trọng lượng 200g.

  1. Nếu $x_1$ là số lẻ thì kết luận NO.
  2. Ngược lại ($x_1$ là số chẵn) thì xét tiếp: nếu $x_2$ là số chẵn thì kết luận YES.
  3. Nếu $x_2$ là số lẻ thì cần đảm bảo có tồn tại tờ 1000 (quả 100g) để thối lại thì mới chia được. Lúc này $x_1$ đang chẵn, nên chỉ cần loại trừ trường hợp $x_1 = 0$ (vì 0 cũng là số chẵn): $x_1 \ne 0$ thì YES, còn $x_1 = 0$ thì NO.
$x_2$ chẵn$x_2$ lẻ
$x_1$ lẻNONO
$x_1$ chẵn, $x_1 > 0$YESYES
$x_1 = 0$YESNO

Việc thống kê số lượng 2 biến $x_1$, $x_2$ ta làm luôn ngay trong quá trình đọc dữ liệu input vào. Và cũng không cần mảng lưu trữ trọng lượng từng quả táo, mà chỉ cần biến $x$ đại diện đọc trọng lượng từng quả rồi kiểm tra điều kiện để tăng biến đếm $x_1$ hay $x_2$ tương ứng.

Minh hoạChia táo cho Tuấn và Sơn

Em nhập trọng lượng các quả táo (100 hoặc 200, tối đa 16 quả) rồi bấm Tiếp để xem từng bước đếm, xét chẵn lẻ và cách chia cụ thể cho 2 bạn:

Ví dụ nhanh:

CodeĐếm khi đọc + xét chẵn lẻ – O(N)

Source code các bạn tham khảo để nhìn thấy rõ với tất cả những gì mình đã phân tích ở trên:

#include <bits/stdc++.h>
using namespace std;
int main(){
    int n, x, x1 = 0, x2 = 0;
    cin >> n;
    while(n--){
        cin >> x;
        if(x == 100){
            x1++;
        }
        else{
            x2++;
        }
    }
    if(x1 % 2 != 0){
        cout << "NO";
    }
    else{
        if(x2 % 2 == 0){
            cout << "YES";
        }
        else{
            if(x1 != 0){
                cout << "YES";
            }
            else{
                cout << "NO";
            }
        }
    }
    return 0;
}
n = int(input())
arr = list(map(int, input().split()))
x1 = x2 = 0
for x in arr:
    if x == 100:
        x1 += 1
    else:
        x2 += 1
if x1 % 2 != 0:
    print("NO")
else:
    if x2 % 2 == 0:
        print("YES")
    else:
        if x1 != 0:
            print("YES")
        else:
            print("NO")
C++▶ Chạy tay từng dòng codeĐếm x1, x2 ngay khi đọc rồi xét chẵn lẻ, O(N).
Dữ liệu mẫu:
1#include <bits/stdc++.h>
2using namespace std;
3int main(){
4 int n, x, x1 = 0, x2 = 0;
5 cin >> n;
6 while(n--){
7 cin >> x;
8 if(x == 100){
9 x1++;
10 }
11 else{
12 x2++;
13 }
14 }
15 if(x1 % 2 != 0){
16 cout << "NO";
17 }
18 else{
19 if(x2 % 2 == 0){
20 cout << "YES";
21 }
22 else{
23 if(x1 != 0){
24 cout << "YES";
25 }
26 else{
27 cout << "NO";
28 }
29 }
30 }
31 return 0;
32}

Đánh giá độ phức tạp của cách làm này:

Không gian$O(1)$
Thời gian$O(N)$ với $N$ là số lượng quả táo

Bài cho $n$ tối đa $10^6$ nên với cách làm trên cũng chỉ cần lặp qua đủ $n$ lần đọc dữ liệu input thôi. Trong trường hợp xấu nhất $n = 10^6$ thì vòng lặp cũng chỉ lặp $10^6$ lần, ít hơn rất nhiều so với ngưỡng giới hạn tiêu chuẩn 1 giây là $(3 \text{ đến } 5) \cdot 10^7$, cao lắm là $10^8$. Nên yên tâm sẽ không bị TLE nhé.

Thực ra cách làm ở trên có thể xem là tối ưu nhất rồi, sẽ không có cách nào tối ưu hơn được đâu. Vì mọi bài dù ít nhất cũng phải làm được bước đọc dữ liệu từ input, còn ở đây bài này chỉ cần đọc dữ liệu xong rồi qua vài câu lệnh kiểm tra điều kiện nữa là ra đáp án luôn, không cần phải lặp thêm gì nữa.

Ghi chú của mình: code C++ và Python ở trên là của anh. Cả 2 bản đã được kiểm tra với 400 bộ test ngẫu nhiên, so với cách vét cạn bằng quy hoạch động tập con (xét mọi tổng có thể ghép được, xem có đạt đúng nửa tổng hay không) – khớp hết. Với $n = 10^6$, bản C++ chạy khoảng 0.11 giây, bản Python khoảng 0.2 giây.
Rất vui khi anh em nào đã đọc đến những dòng cuối cùng này. Anh em đọc chắc cũng mỏi mắt luôn chứ 😅 vậy anh em nghĩ người viết ra hết đống ở trên còn thế nào nữa 🤪. Vậy nên đừng tiếc gì cho mình một lời bình luận hoặc chia sẻ bài viết để động viên mình nè. Chúc anh em học tốt và gặt hái nhiều thành công 😇

Nhận xét