Tổng Các Số Chẵn Liên Tiếp Lớn Nhất (TONGCHAN)
Đề bài
Bài 2. (6 điểm) Tổng các số chẵn liên tiếp lớn nhất – TONGCHAN
Cho một dãy $n$ số nguyên.
Yêu cầu: Tìm dãy con liên tiếp các số chẵn có tổng lớn nhất. Dãy con tìm được phải có ít nhất 02 phần tử trở lên.
Input: Gồm hai dòng:
- Dòng đầu ghi một số nguyên dương $n$ $(2 \le n \le 10^6)$ là số lượng phần tử trong dãy;
- Dòng thứ hai ghi $n$ số nguyên $a_1, a_2, \ldots, a_n$ $(|a_i| \le 10^6)$ là giá trị từng phần tử trong dãy.
Output: Dãy con tìm được. Nếu có nhiều dãy con tìm được thì chỉ xuất dãy con đầu tiên. Nếu không tìm được dãy con nào thì xuất số $-1$.
Tên file: TONGCHAN.INP / TONGCHAN.OUT (khi chạy thử / chấm online thì đọc dữ liệu từ bàn phím và in ra màn hình như bình thường nhé).
10
3 2 2 4 5 6 8 3 8 46 8Giải thích (của đề): Có 3 dãy con chứa liên tiếp các số chẵn là: (2 2 4), (6 8) và (8 4). Trong đó, dãy (6 8) có tổng lớn nhất là 14.
Lời mở đầu
Trong quá trình đi comment dạo mình thấy bài tập này cũng hay nên chia sẻ đề lên đây để anh em cùng xem và thử sức nhé. Bạn đăng bài hỏi "thuật toán tối ưu" của bài này – thì cách tối ưu là chỉ cần duyệt qua danh sách các số 1 lần là ra đáp án, độ phức tạp $O(N)$ với $N$ là độ dài của dãy số. Đặc biệt trong bài này mình sẽ chỉ anh em một mẹo nhỏ để code gọn hơn hẳn và không lo bị văng lỗi truy cập ngoài mảng 😉.
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é:
Bấm vào nút bên dưới để nộp bài và được hệ thống CLUE OJ chấm tự động.
Nộp bài trên CLUE OJHiểu đề qua ví dụ: dãy 3 2 2 4 5 6 8 3 8 4 bị các số lẻ (3, 5, 3) "cắt" thành các khúc toàn số chẵn: (2 2 4) tổng 8, (6 8) tổng 14, (8 4) tổng 12. Như phần giải thích của đề, mỗi khúc chẵn được xét trọn vẹn (từ ngay sau một số lẻ đến ngay trước số lẻ tiếp theo), khúc nào có từ 2 phần tử trở lên và tổng lớn nhất thì in ra – ở đây là 6 8. Nếu không có khúc chẵn nào dài từ 2 trở lên (ví dụ 1 2 3 4) thì in $-1$.
Lộ trình lời giải, bấm vào từng phần để nhảy tới:
Cách 1Duyệt một lần, nhận biết đầu – cuối khúc chẵn – O(N)
Đầu tiên tạo ra 2 biến startMax, endMax mục đích lưu trữ vị trí bắt đầu và vị trí kết thúc của dãy số chẵn liên tiếp có tổng lớn nhất. Ban đầu khởi tạo giá trị 2 biến này là $-1$.
Tư tưởng giải quyết là ta phải nhìn ra được mấu chốt: khi xét phần tử $a[i]$ hiện tại, nếu nó là số chẵn thì:
- Ta phải biết xem liệu nó có là khởi đầu của dãy số chẵn liên tiếp không? Để biết được thì chỉ đơn giản: nếu số liền trước nó $a[i - 1]$ là số lẻ thì chính nó hiện tại là mở đầu của dãy chẵn ⇒ lưu lại vị trí của nó vào biến
start. - Cứ với mỗi giá trị $a[i]$ là chẵn thì ta cộng giá trị của nó vào biến tổng
sum. - Tương tự, nếu đằng sau nó $a[i + 1]$ là số lẻ thì $a[i]$ hiện tại chính là số chẵn cuối cùng của dãy liên tiếp. Có được vị trí mở đầu, vị trí kết thúc ta tính ra được độ dài của dãy bằng công thức:
cuối - đầu + 1. Nếu độ dài thỏa mãn $\ge 2$ theo như đề bài yêu cầu thì ta so sánh biến tổng với giá trịMax, nếu lớn hơnMaxthì cập nhật lại, đồng thời cập nhậtstartMax,endMaxlưu lại vị trí đầu, cuối của dãy thỏa mãn. - Khi đã tìm được vị trí kết thúc của dãy thì nhớ reset lại biến tổng = 0 để các lần tiếp theo gặp dãy thỏa mãn thì quy trình lặp lại.
Cứ như thế đến sau cùng ta kiểm tra: nếu startMax vẫn mang giá trị $-1$ như ban đầu thì kết luận không tìm được dãy nào thỏa mãn; nếu khác $-1$ thì in ra các phần tử của dãy từ vị trí startMax đến endMax, đó chính là đáp án.
sum > Max. Còn nếu đề yêu cầu lấy kết quả cuối cùng thì lúc đó ta mới so sánh sum >= Max (lấy luôn trường hợp bằng để đảm bảo nó là kết quả sau cùng).Max ta khởi tạo với giá trị nhỏ nhất có thể để đảm bảo lần so sánh đầu tiên luôn thỏa lớn hơn nó để mà cập nhật lại. Đề có tối đa $10^6$ phần tử, mỗi phần tử nhỏ nhất là $-10^6$, nên nếu cả dãy đều là $-10^6$ thì tổng sẽ là $-10^{12}$. Vì thế ta khởi tạo Max $= -10^{13}$ để dù tình huống đó có xảy ra thì biến tổng vẫn lớn hơn Max. Và tất nhiên sum, Max phải là long long vì tổng có thể tới $10^{12}$ – int là tràn ngay.Mẹo "lính canh": mảng n + 2 phần tử
Khi xét $a[i]$ là số chẵn thì phải xét $a[i - 1]$ và $a[i + 1]$. Phải lưu ý nếu $i$ đang là 0 thì gọi $a[i - 1]$ tức là gọi $a[-1]$ thì sẽ bị văng lỗi ngay nhé (index tối thiểu từ 0 chứ không có chuyện âm); tương tự nếu $i$ đang là $n - 1$ thì gọi $a[i + 1]$ tức là gọi $a[n]$ cũng sẽ bị lỗi (mảng $n$ phần tử thì index hợp lệ chỉ là từ 0 đến $n - 1$). Vậy nên cần xét riêng 2 tình huống $i = 0$ và $i = n - 1$ – chỗ này nếu không biết xử lý khéo thì sẽ phải code dài thêm.
Minh hoạ duyệt mảng
Các bạn nhập dãy rồi bấm Tiếp để xem từng bước (ô viền nét đứt: lính canh; vàng: khúc chẵn đang cộng dồn; xanh: khúc tốt nhất hiện tại):
Source code để các bạn tham khảo nếu làm theo hướng này:
#include <iostream>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
int n;
cin >> n;
int *a = new int[n + 2];
a[0] = a[n + 1] = 1;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
}
long long Max = -10000000000000;
int start = -1;
int startMax = -1, endMax = -1;
long long sum = 0;
for (int i = 1; i <= n; ++i) {
if (a[i] % 2 == 0) {
sum += a[i];
if (a[i - 1] % 2 != 0) {
start = i;
}
if (a[i + 1] % 2 != 0) {
int cnt = i - start + 1;
if (cnt >= 2) {
if (sum > Max) {
Max = sum;
startMax = start;
endMax = i;
}
}
sum = 0;
}
}
}
if (startMax == -1) {
cout << -1;
}
else {
for (int i = startMax; i <= endMax; ++i) {
cout << a[i] << " ";
}
}
delete[] a;
return 0;
}
import sys
data = sys.stdin.buffer.read().split()
n = int(data[0])
a = [1] + [int(x) for x in data[1:n + 1]] + [1]
Max = -10 ** 13
start = -1
startMax = endMax = -1
s = 0
for i in range(1, n + 1):
if a[i] % 2 == 0:
s += a[i]
if a[i - 1] % 2 != 0:
start = i
if a[i + 1] % 2 != 0:
cnt = i - start + 1
if cnt >= 2 and s > Max:
Max = s
startMax = start
endMax = i
s = 0
if startMax == -1:
print(-1)
else:
print(' '.join(map(str, a[startMax:endMax + 1])))
" " chuẩn. Mình thêm 2 dòng ios::sync_with_stdio(false); cin.tie(0); cho đọc $10^6$ số nhanh hơn, còn lại logic giữ nguyên. Bản Python đọc toàn bộ input một lần bằng sys.stdin.buffer cho nhanh.Khung chạy tay (để dễ theo dõi, giới hạn $n \le 15$):
Đánh giá độ phức tạp của cách làm này:
- Không gian: mảng $n + 2$ phần tử – $O(N)$ (cần lưu lại để cuối cùng in khúc tốt nhất).
- Thời gian: đọc mảng 1 lần, duyệt 1 lần, in 1 lần ⇒ $O(N)$.
a % 2 == 0, còn số lẻ âm thì a % 2 là $-1$ (C++) hoặc $1$ (Python) – đều khác 0. Vì vậy mình viết điều kiện "lẻ" là % 2 != 0 chứ không phải % 2 == 1, để số lẻ âm như $-3$ vẫn được nhận đúng là số lẻ.6 8. Với $n = 10^6$, bản C++ chạy khoảng 0.2 giây, bản Python khoảng 1.2 – 2 giây. Lời giải hiểu "dãy con liên tiếp các số chẵn" là cả khúc chẵn nằm giữa hai số lẻ, đúng như phần giải thích ví dụ của đề.- Không biết học gì trước, học gì sau, học lan man nên kiến thức bị hổng.
- Bí bài không ai gỡ, ngồi cả buổi vẫn không ra rồi bỏ cuộc.
- Code chạy được nhưng không đủ điểm, không biết sai ở đâu, chậm ở đâu.
- Không có đề thi thử và người chấm, nên không biết mình đang ở mức nào.
- Dễ nản giữa chừng khi không có ai theo sát.
- Lộ trình 111 buổi video sắp sẵn từ dòng code đầu tiên đến đề HSG thật của các tỉnh.
- Thầy trực tiếp gỡ bí, chữa bài cho từng em qua nhóm Zalo riêng.
- Hệ thống chấm bài tự động 24/7 và thi thử HSG hằng tuần để biết chính xác mình đang ở đâu.
- Đóng học phí một lần, thầy kèm lâu dài: từ thi chuyên Tin đến HSG quốc gia và cả khi lên đại học.
Nhận xét
Đăng nhận xét