[C++, programare dinamică] Problema submatricei de sumă maximă pe R

Problema este de la finalul acestei pagini (fragment din carte).


c) Se dă o matrice și se cere determinarea unui dreptunghi de sumă maximă. Ultimul algoritm prezentat poate fi extins pentru rezolvarea problemei în O(N3). Cum?



Ultimul algoritmul prezentat în pagina de la linkul de mai sus este:
  • maximul global este inițial primul număr.
  • suma maximă ce se poate obține dintr-o subsecvență care se termină pe poziția primului element este primul element
  • pentru fiecare element de la al doilea până la sfârșit
    • suma maximă a unei subsecvențe ce se termină pe poziția curentă este fie valoarea elementului curent, fie suma obținută dintr-o subsecv. ce se termină pe poziția anterioară + valoarea elementului curent
    • dacă suma maximă obținută pe poziția curentă este > decât maximul global, maximul global devine suma curentă



Problemă similară la sfârșitul capitolului:

  1. Scrieți un program care răspunde eficient la mai multe interogări privind suma unor dreptunghiuri ale unei matrice.

rezolvată ineficient aici. Deosebirea de această problemă pe care am rezolvat-o deja este că nu se cer sume de dreptunghiuri ci un dreptunghi de sumă maximă.
  • nu trebuie să calculez toate sumele ci să calculez inteligent doar sumele necesare dreptunghiului de sumă maximă, dacă e posibil.



Articolul prezent este inspirat din acest articol.

Dată fiind o matrice, găsiți submatricea de sumă maximă din ea. De exemplu, în următoarea matrice, submatricea de sumă maximă este evidențiată cu dreptunghi albastru și suma acestei submatrice este 29.



Problema este mai ales o extensie a problemei subsecvenței de sumă maximă a unui vector.

Soluția naivă pentru această problemă este să se verifice fiecare dreptunghi posibil în matricea dată. Această soluție cere 4 cicluri îmbricate și complexitatea timp a acestei soluții ar fi O(N4). [ n.e. Aici am rezolvat naiv problema. ]

Algoritmul lui Kadane pentru un vector poate fi folosit pentru a reduce complexitatea timp la O(N3). Ideea este să se fixeze coloanele din stânga și din dreapta una câte una și să se găsească rândurile consecutive de sumă maximă pentru fiecare pereche de coloane, una din stânga și una din dreapta. Practic se găsesc numerele de rând de sus și de jos (care au suma maximă) pentru fiecare pereche fixată de coloane din stânga și dreapta. Pentru a găsi numerele de rânduri de sus și de jos, se calculează suma elementelor în fiecare rând de la stânga la dreapta și se stochează aceste sume într-un vector, de exemplu temp[]. Deci temp[i] indică suma elementelor de la stânga la dreapta în rândul i. Dacă se aplică algoritmul lui Kadane pe temp[], și găsim subvectorul de sumă maximă a lui temp, această suma maximă ar fi suma maximă posibilă cu stânga și dreapta ca fiind coloanele mărginitoare. Pentru a găsi suma maximă finală, comparăm această sumă cu suma maximă de până acum.


Metoda descrisă în articol funcționează pentru oricare numere reale.

Cod sursă

#include <fstream>
#include <vector>
using namespace std;

double subsecv_de_suma_maxima(vector<double> &A,
       int &inceput, int &sfarsit)
{
       int N = A.size() - 1;
       double max = A[1];
       int inceput_temp = 1;
       vector<double> S(N + 1);
       S[1] = A[1];
       inceput = sfarsit = 1;

       for (int i = 2; i <= N; ++i)
       {
              if (S[i - 1] + A[i] > A[i])
              {
                     S[i] = S[i - 1] + A[i];
              }
              else
              {
                     inceput_temp = i;
                     S[i] = A[i];
              }

              if (S[i] > max)
              {
                     inceput = inceput_temp;
                     sfarsit = i;
                     max = S[i];
              }
       }

       return max;
}

double suma_max_in_dreptunghi(vector<vector<double>> &D, int r, int c,
       int &stanga, int &dreapta, int &sus, int &jos)
{
       double suma_max = D[1][1];
       for (int st = 1; st <= c; ++st)
       {
              vector<double> temp(r + 1, 0);

              // cand dr == st, e o submatrice-coloana
              for (int dr = st; dr <= c; ++dr)
              {
                     // pt. fiecare rand se adauga elementul curent
                     // din el la elementul corespunzator din temp
                     for (int i = 1; i <= r; ++i)
                     {
                           temp[i] += D[i][dr];
                     }

                     int inceput, sfarsit;

                     double suma = subsecv_de_suma_maxima(temp, inceput, sfarsit);

                     if (suma > suma_max)
                     {
                           suma_max = suma;
                           stanga = st;
                           dreapta = dr;
                           sus = inceput;
                           jos = sfarsit;
                     }
              }
       }
       return suma_max;
}

int main()
{
       int r, c;

       ifstream in("suma.in");
       in >> r >> c;
       vector<vector<double>> D(r + 1, vector<double>(c + 1));
       for (int i = 1; i <= r; ++i)
       {
              for (int j = 1; j <= c; ++j)
              {
                     in >> D[i][j];
              }
       }
       in.close();

       int stanga, dreapta, sus, jos;
       double suma_maxima = suma_max_in_dreptunghi(
              D, r, c, stanga, dreapta, sus, jos);

       ofstream out("suma.out");
       out << suma_maxima << endl;
       out << "(" << sus << ", " << stanga << ")" << endl;
       out << "(" << jos << ", " << dreapta << ")" << endl;
       out.close();

       return 0;
}

Intrare

3 4
0.5 0.1 0.8 -0.9
0.4 0.2  6   0.3
4   0.8  0.9 1

Ieșire

14.1
(1, 1)
(3, 4)

Prezentare

Schemă


[C++, programare dinamică] Problema subsecvenței de produs maxim pe R

Problema este de la finalul acestui fragment din cartea Algoritmica C++ de Vlad Sebastian Ionescu, Eugen Laslo.

Enunțul problemei
b) Se cere o subsecvență de produs maxim, iar numerele sunt reale. Rezolvați problema atât pentru numere strict pozitive cât și pentru numere nenule (dar care pot fi negative).

Pentru două rezolvări care se aplică doar asupra numerelor pozitive, sau respectiv doar asupra numerelor întregi, vedeți aici. Tot acolo găsiți intrări de test.

Cod sursă
Soluție inspirată de aici care funcționează în toate cazurile:
  • Nr. întregi: pozitive și negative.
  • Nr. cu virgulă: pozitive și negative.
  • Zero.
Și află și pozițiile de început și de sfârșit ale subsecvenței găsite.


float subsecv(float A[], int N,
       int &inceput, int &sfarsit)
{
       inceput = sfarsit = 1;
       int inceput_pozitiv_temp = 1,
              inceput_negativ_temp = 1;
       vector<float> p(N + 1, 0),
              n(N + 1, 0);
       int zero_index = 0;
       if (A[1] == 0)
       {
              p[1] = n[1] = 0;
              zero_index = 1;
       }
       else if (A[1] > 0)
       {
              p[1] = A[1];
              n[1] = 0;
       }
       else // A[1] < 0
       {
              p[1] = 0;
              n[1] = A[1];
       }
       float maximul = A[1];
       for (int i = 2; i <= N; ++i)
       {
              if (A[i] == 0)
              {
                     p[i] = n[i] = 0;
                     if (zero_index == 0)
                     {
                           zero_index = i;
                     }
              }
              else if (A[i] > 0)
              {
                     if (A[i] > A[i] * p[i - 1])
                     {
                           p[i] = A[i];
                           inceput_pozitiv_temp = i;
                     }
                     else
                     {
                           p[i] = A[i] * p[i - 1];
                     }
                     n[i] = A[i] * n[i - 1];
              }
              else // A[i] < 0
              {
                     p[i] = A[i] * n[i - 1];
                     inceput_pozitiv_temp = inceput_negativ_temp;
                     if (A[i] < A[i] * p[i - 1])
                     {
                           n[i] = A[i];
                           inceput_negativ_temp = i;
                     }
                     else
                     {
                           n[i] = A[i] * p[i - 1];
                           inceput_negativ_temp = inceput_pozitiv_temp;
                     }
              }
              if (p[i] > maximul)
              {
                     maximul = p[i];
                     inceput = inceput_pozitiv_temp;
                     sfarsit = i;
              }
       }
       if (maximul > 0)
       {
              return maximul;
       }
       if (zero_index != 0)
       {
              inceput = sfarsit = zero_index;
              return 0;
       }
       inceput = sfarsit = 1;
       return A[1];
}

Explicații
Dată fiind prima soluție de aici (titlul „Cod sursă”) se încearcă adaptarea ei pentru a funcționa și pentru numere negative și zero.

Dat fiind vectorul A cu indici de la 1 la N, lăsăm p[i] să fie produsul maxim pozitiv al unei subsecvențe din A[1, i] conținând A[i], și lăsăm pe n[i] să fie produsul minim negativ al unei asemenea subsecvențe. Dacă nici o asemenea subsecvență nu există, lăsăm p[i] = 0 sau n[i] = 0.

Inițializare:
  • dacă A[1] = 0, atunci p[1] = n[1] = 0
  • dacă A[1] > 0, atunci p[1] = A[1] și n[1] = 0
  • dacă A[1] < 0, atunci p[1] = 0 și n[1] = A[1]

Iterare:
  • dacă A[i] = 0, atunci p[i] = n[i] = 0
  • dacă A[i] > 0, atunci p[i] = max(A[i], A[i] * p[i - 1]) și n[i] = A[i] * n[i - 1]
  • dacă A[i] < 0, atunci p[i] = A[i] * n[i - 1] și n[i] = min(A[i], A[i] * P[i - 1])

Dacă max(p[1], ..., p[N]) > 0, atunci acesta este răspunsul. Unicul mod în care aceasta nu are loc este dacă nu există o subsecvență al cărei produs să fie pozitiv. Aceasta poate avea loc doar dacă vectorul conține doar numere negative și zerouri, și fiecare două numere negative sunt separate prin cel puțin un zero. Dacă există cel puțin un zero, atunci răspunsul este zero. Dacă nu există zerouri, atunci N = 1 și răspunsul este A[1].