Hướng dẫn giải của Dãy con và đoạn con
Chỉ dùng lời giải này khi không có ý tưởng, và đừng copy-paste code từ lời giải này. Hãy tôn trọng người ra đề và người viết lời giải.
Nộp một lời giải chính thức trước khi tự giải là một hành động có thể bị ban.
Nộp một lời giải chính thức trước khi tự giải là một hành động có thể bị ban.
Lời giải này đang bị ẩn cho đến khi bạn chọn mở ra.
Chúng tôi khuyên bạn nên tự thử giải bài trước. Việc mở lời giải có thể làm lộ mất ý tưởng chính trước khi bạn có cơ hội tự giải.
Bạn phải đăng nhập để mở lời giải này.
Đăng nhậpTác giả: , ,
Editorial for sub: Dãy con và đoạn con
Hiểu bài toán
Bài toán yêu cầu tìm hai giá trị trên một dãy số:
- Tổng lớn nhất của dãy con khác rỗng: Dãy con là tập hợp các phần tử không nhất thiết liền kề nhau. Để có tổng lớn nhất, ta chỉ cần cộng tất cả các số dương trong dãy lại với nhau. Nếu dãy chỉ toàn số âm, ta phải chọn số âm lớn nhất (gần 0 nhất).
- Tổng lớn nhất của đoạn con khác rỗng: Đoạn con là các phần tử liền kề nhau. Đây là bài toán quy hoạch động kinh điển (Maximum Subarray Sum - Kadane's Algorithm).
Các cách tiếp cận
Cách Tối ưu hóa Logic (Dựa trên Solution 1)
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
const int N = 1e6 + 10;
int max(int a, int b){
return (a > b ? a : b);
}
int main(){
int t; scanf("%d", &t);
while(t--){
int n; scanf("%d", &n);
int sum = 0, res = 0;
int best = INT_MIN;
int neg = INT_MIN, cneg = 0;
for(int i = 1, a; i <= n; ++i){
scanf("%d", &a);
if(a >= 0) res += a;
else{
++cneg;
neg = max(neg, a);
}
sum = max(a, sum + a);
best = max(best, sum);
}
if(cneg == n){
printf("%d %d\n", neg, neg);
}else printf("%d %d\n", res, best);
}
return 0;
}
- Time Complexity: O(N)
- Space Complexity: O(1)
Phương pháp này giải quyết cả hai phần của bài toán trong một vòng lặp duy nhất.
- Phần 1 (Dãy con): Biến
rescộng dồn tất cả các số dương (if(a >= 0)). Nếu tất cả đều là số âm (cneg == n), kết quả là số âm lớn nhất (được lưu trongneg). - Phần 2 (Đoạn con): Sử dụng biến
sumđể lưu tổng đoạn con kết thúc tại vị trí hiện tại, vàbestđể lưu tổng lớn nhất tìm được. Logic:sum = max(a, sum + a). Nếusumtại hiện tại âm thì reset vềa(bắt đầu đoạn mới).
Cách Phân tích Rõ ràng (Dựa trên Solution 2)
#include<stdio.h>
#include<math.h>
int main(){
int t;
scanf("%d", &t);
while(t--){
int n;
scanf("%d", &n);
int a[n];
for(int i=0; i<n; i++){
scanf("%d", &a[i]);
}
long long sum1 = 0, sum2 = -1e9, max = -1e9, sum3 = 0;
for(int i=0; i<n; i++){
sum1 += a[i];
if(sum1>sum2)
sum2 = sum1;
if(sum1<0)
sum1 = 0;
}
for(int i=0; i<n; i++){
if(a[i]>max)
max = a[i];
}
if(max>0){
for(int i=0; i<n; i++){
if(a[i]>0)
sum3 += a[i];
}
}
else
sum3 = max;
printf("%lld %lld\n", sum3, sum2);
}
}
- Time Complexity: O(N)
- Space Complexity: O(N)
Cách tiếp cận này tách biệt logic hai phần:
- Đoạn con (sum2): Áp dụng thuật toán Kadane chuẩn.
sum1là tổng hiện tại, reset về 0 nếu âm.sum2lưu max. - Dãy con (sum3): Tìm giá trị lớn nhất (
max) trước. Nếumax > 0, duyệt lại mảng để cộng các số dương. Nếumax <= 0, gánsum3 = max. Lưu ý: Solution 2 khai báo biếnsum2vàmaxvới giá trị ban đầu-1e9(kiểu int) có thể gây lỗi số học nếu tổng vượt quá giới hạn, nhưng trong đề bài thì an toàn.
Cách Quy hoạch động Chuẩn (Phân tích thêm)
#include <iostream>
#include <vector>
#include <numeric>
#include <algorithm>
using namespace std;
void solve() {
int n;
cin >> n;
vector<int> a(n);
long long max_sub_sum = -1e18;
long long cur_sum = 0;
long long pos_sum = 0;
bool has_positive = false;
int max_neg = -1e9;
for (int i = 0; i < n; i++) {
cin >> a[i];
// 1. Dãy con (tổng số dương)
if (a[i] > 0) {
pos_sum += a[i];
has_positive = true;
} else {
max_neg = max(max, a[i]));
}
// 2. Đoạn con (Kadane)
cur_sum += a[i];
if (cur_sum > max_sub_sum) max_sub_sum = cur_sum;
if (cur_sum < 0) cur_sum = 0;
}
long long ans1 = has_positive ? pos_sum : max_neg;
cout << ans1 << " " << max_sub_sum << "\n";
}
int main() {
ios_base::sync_with_stdio(false); cin.tie(NULL);
int t; cin >> t;
while(t--) solve();
return 0;
}
- Time Complexity: O(N)
- Space Complexity: O(N)
Đây là phiên bản C++ hiện đại hóa của Logic 1, sử dụng long long để an toàn số học dù đề bài dùng int.
- Dãy con: Duyệt một lần, nếu
a[i] > 0thì cộng vàopos_sum, ngược lại cập nhậtmax_neg. Cuối cùng chọnpos_sumnếu có số dương, ngược lại chọnmax_neg. - Đoạn con: Dùng biến
cur_sum(tổng hiện tại) vàmax_sub_sum. Nếucur_sum < 0thì reset về 0 (bắt đầu đoạn mới từ phần tử tiếp theo).
Phân tích độ phức tạp
| Cách tiếp cận | Time | Space | Tên |
|---|---|---|---|
| 1 | O(N) | O(1) | Tối ưu hóa Logic (Dựa trên Solution 1) |
| 2 | O(N) | O(N) | Phân tích Rõ ràng (Dựa trên Solution 2) |
| 3 | O(N) | O(N) | Quy hoạch động Chuẩn (Phân tích thêm) |
Bài học kinh nghiệm
- Bài toán dãy con (tổng lớn nhất) chỉ đơn giản là tổng các số dương. Quy tắc 'khác rỗng' bắt buộc phải chọn ít nhất 1 phần tử, nên nếu không có số dương ta phải chọn số âm lớn nhất.
- Bài toán đoạn con (tổng lớn nhất) có thể giải quyết trong O(N) bằng thuật toán Kadane. Logic cốt lõi là 'nếu tổng tích lũy hiện tại âm, ta nên bắt đầu một đoạn mới từ phần tử hiện tại'.
- Có thể kết hợp cả hai bài toán con vào cùng một vòng lặp duyệt mảng để tối ưu về mặt code và tốc độ.
Lỗi thường gặp
- Trường hợp toàn số âm: Cần xử lý riêng cho cả hai phần. Nếu không, phần dãy con sẽ trả về 0 (sai do rỗng) và phần đoạn con có thể sai nếu không khởi tạo đúng.
- Sai số kiểu dữ liệu: Mặc dù Input nằm trong
int, nhưng tổng của dãy con có thể lên tới 10^9 (10^5 * 10^4). Khi dùng C/C++, nên dùnglong longcho biến lưu tổng để tránh tràn số nếu test case lớn hơn một chút so với mô tả. - Khởi tạo biến: Với Kadane algorithm, biến lưu tổng lớn nhất phải được khởi tạo bằng giá trị âm vô cùng (ví dụ: -1e18) hoặc phần tử đầu tiên của mảng.
Bình luận