BÀI TOÁN
Cho một dãy gồm n
số nguyên a1, a2, …, an. Hãy tìm một đoạn con (dãy gồm các phần tử liên tiếp
nhau) có tổng lớn nhất.
Dữ liệu vào:
- Dòng đầu ghi số n
- Dòng thứ hai ghi n số nguyên a1, a2, …, an, mỗi số có giá trị tuyệt đối không vượt quá 1000.
Kết quả:
Ví dụ:
Input
10
1 3 -5 2 7 6 -2 4 -3 1
Output
17
Giới hạn:
- 50% số test có n ≤ 103
- 50% số test còn lại có n ≤ 106
HƯỚNG DẪN
Cách tiếp cận đơn giản nhất ta tìm tổng từng đoạn và so sánh
các kết quả này để tìm ra kết quả lớn nhất. Độ phức tạp cho cách này là O(n3),
cách này chưa thể giải quyết cho bài toán trên.
Để cải tiến thuật toán trên, ta tính nhanh tổng của một đoạn bất kỳ
bằng cách làm như sau:
- Gọi f[i] là tổng i phần tử đầu tiên, f[i] được tính như sau:
- f[0] = 0
- f[i] = f[i-1] + a[i]
- Tổng các phần tử từ i đến j là: f[j] – f[i-1]
Với cách cải tiến, độ phức tạp của thuật toán giảm từ
O(n3) xuống còn O(n2) và ta có thể giải quyết được 50% số
test của bài toán trên.
Để có thể giải quyết được 100% số test trên ta có 2 cách làm
với độ phức tạp O(n) như sau:
Cách 1
Ta sử dụng biến sum để giữ giá trị tổng tiềm năng của các phần
tử liên tục phía trước (khi thêm tổng này vào đoạn mới phía sau ta luôn nhận được
một đoạn có tổng lớn hơn). Ta nhận thấy khi giá trị của sum < 0 thì ta sẽ cập
nhật lại giá trị sum = 0 (bỏ đi đoạn phía trước).
Đoạn code cho ý tưởng trên như sau:
int maxsum = 0;
int sum = 0;
for (int i = 1; i <= n; ++i) {
sum += a[i];
if (sum < 0) sum = 0;
maxsum = max(sum, maxsum);
}
Cách 2 (thanks to Xuân Quang and Văn Thiên)
Ta nhận thấy, tại mỗi vị trí thứ i tổng của đoạn lớn nhất kết
thúc tại i là f[i] – min(f[j] | j = 0..i).
Khi đó, ta lần lượt xét các vị trí i, tại mỗi vị trí thứ i ta giữ lại giá
trị nhỏ nhất của các f[j].
Đoạn code cho ý tưởng trên như sau:
f[0] = 0;
for (int i = 1; i
<= n; ++i) f[i] = f[i-1] + a[i];
int maxsum = 0,
f_min = 0;
for (int i = 1; i
<= n; ++i) {
f_min =
min(f_min, f[i]);
maxsum =
max(maxsum, f[i] - f_min);
}
trong trường hợp tất cả các số mà âm thì Cách 1 ra 0 lại sai kết quả
ReplyDeletesửa chút thôi bạn
Deleteint maxsum = -maxlongint;
int sum = 0;
for (int i = 1; i <= n; ++i) {
sum += a[i];
maxsum = max(sum, maxsum);
if (sum < 0) sum = 0;
}
Thuật toán này có tên không bạn cho mk xin với
DeleteSai đâu, nếu tất cả các số âm thì đoạn con đó không có phần tử nào, tổng vẫn bằng 0 mà
ReplyDeleteulatr, đề bài bảo tìm tổng lớn nhất của dãy con liên tiếp, thế lấy 0 là thế nào? Tôi nghĩ nếu trường hợp âm hết thì lấy số âm nhỏ nhất chứ nhỉ, sao lại lấy 0 đc, có số 0 trong mảng đó đâu mà chọn làm dãy con cơ chứ?
Deletef_min = min(f_min, f[i]); mình chưa hiểu chổ này lắm
ReplyDelete6
Delete-12 7 11 -8 7 9
nó ra 26
thuật sai mẹ rồi!
thuật toán đúng mà bạn
Delete-12 7 11 -8 7 9
đoạn con có giá trị lớn nhất là 7 11 -8 7 9
ra 26 đúng rồi
a ơi.trường hợp mảng âm hết nó sai mất
ReplyDeletebạn cho mk cái ví dụ nào,âm cả thì in ra số lớn nhất thôi
Deleteban nao cho minh xin code full duoc k a
ReplyDeleteCách 1:
Deletevar k,n,i,s,max:longint;
begin
readln(n);
for i:=1 to n do
begin
read(k);
s:=s+k;
if s<0 then s:=0;
if s>max then max:=s;
end;
write(max);
readln;
readln;
end.
Cách 2:
Deletevar k,n,i,s,max,min:longint;
a,f:array[0..1000000] of longint;
begin
readln(n);
for i:=1 to n do
begin
read(a[i]);
f[i]:=f[i-1]+a[i];
end;
max:=0;
min:=0;
for i:=1 to n do
begin
if f[i]max then max:=f[i]-min;
end;
write(max);
readln;
readln;
end.
Cho mình xin bài giải bài toán tìm k phần tử liên tiếp lớn nhất
ReplyDeleteCho mình xin bài giải bài toán tìm k phần tử liên tiếp lớn nhất trong dãy. Mình cảm ơn nhiều
ReplyDeleteThuật toán này có tên không các bạn,cái thuật toán cách 1 của ad á.cho mình xin với
ReplyDelete```
ReplyDelete#include
#define FOR(i,a,b) for(int i =a; i <=b;i++)
using namespace std; //thuat toan kadane
/*inp:
10 1 3 -5 2 7 6 -2 4 -3 1
output: 17
2nd : 9 -2 1 -3 4 -1 2 1 -5 4
output: 6
*/
int main(){
int n; cin >> n;
int a[n+1]; a[0] = 0;
FOR (i,1,n){
cin >> a[i];
}
int currentSum = a[1];
int maxSum = a[1];
int Start = 1;
int End = 1;
int temp = 1;
for (int i = 2; i <= n;i ++){
if (currentSum + a[i] > a[i]){
currentSum = currentSum + a[i];
}
else {
currentSum = a[i];
temp = i;
} // lam gia su cho ham max
if (currentSum > maxSum){
maxSum = currentSum;
Start = temp;
End = i;
```
}
}
cout << maxSum << endl;
cout << Start << ' ' << End;
return 0;
}
cũng là thuật toán Kanade nhưng ở đây có thêm nâng cao chính là điểm bắt đầu của dãy con lớn nhất (Nếu như dãy toàn là số âm thì đơn giản chỉ cần lấy thằng lớn nhất vì cộng thằng nào vào cũng ra số bé hơn)