Monday, January 25, 2016

Đoạn con có tổng lớn nhất

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ả:

Ghi ra 1 số là tổng lớn nhất tìm được.

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 if[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);
    }


17 comments:

  1. 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ả

    ReplyDelete
    Replies
    1. sửa chút thôi bạn
      int maxsum = -maxlongint;
      int sum = 0;
      for (int i = 1; i <= n; ++i) {
      sum += a[i];
      maxsum = max(sum, maxsum);
      if (sum < 0) sum = 0;
      }

      Delete
    2. Thuật toán này có tên không bạn cho mk xin với

      Delete
  2. Sai đâ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à

    ReplyDelete
    Replies
    1. ulatr, đề 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ứ?

      Delete
  3. f_min = min(f_min, f[i]); mình chưa hiểu chổ này lắm

    ReplyDelete
    Replies
    1. 6
      -12 7 11 -8 7 9
      nó ra 26
      thuật sai mẹ rồi!

      Delete
    2. thuật toán đúng mà bạn
      -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

      Delete
  4. a ơi.trường hợp mảng âm hết nó sai mất

    ReplyDelete
    Replies
    1. bạn cho mk cái ví dụ nào,âm cả thì in ra số lớn nhất thôi

      Delete
  5. ban nao cho minh xin code full duoc k a

    ReplyDelete
    Replies
    1. Cách 1:
      var 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.

      Delete
    2. Cách 2:
      var 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.

      Delete
  6. 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

    ReplyDelete
  7. 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 trong dãy. Mình cảm ơn nhiều

    ReplyDelete
  8. Thuậ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
  9. ```
    #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)

    ReplyDelete