Cod sursa(job #2962579)

Utilizator popashtefan10Popa Stefan popashtefan10 Data 8 ianuarie 2023 19:03:36
Problema ADN Scor 100
Compilator cpp-64 Status done
Runda Arhiva de probleme Marime 5.36 kb
// https://www.infoarena.ro/job_detail/2962539
// Complexitate timp: O(N^2 * (L + 2^N)), L = lungimea maxima a unui sir
// Complexitate spatiu: O(L + N * (L + 2^N))

#include <iostream>
#include <cstdio>
#include <cstring>
#define NMAX 18
#define LMAX 30000

using namespace std;

int n; // numarul de siruri
int urm[2 * LMAX + 5]; // vector folosit pt algoritmul KMP, urm[i] = lungimea celui mai mare prefix care este si sufix al subsirului [0..i]
int l[NMAX + 1]; // l[i] = lungimea sirului i
int pref[NMAX + 1][NMAX + 1]; // pref[i][j] = lungimea celui mai mare sufix al sirului i care este prefix al sirului j
// d[bitmask][i][0] = lungimea minima a sirului care contine sirurile din bitmask si se termina cu sirul i
// d[bitmask][i][1] = indicele sirului care se afla inaintea sirului i
int d[(1 << NMAX) + 1][NMAX + 1][2]; // tin minte si de unde am venit
char s[NMAX + 1][LMAX + 5]; // s[i] = sirul i
char p[2 * LMAX + 10]; // sirul pattern folosit pentru a calcula matricea pref

// Complexitate: O(L), unde L = lungimea sirului pattern
int prefix(char *pattern, int lp) {
  // intoarce lungimea celui mai mare prefix care este si sufix al subsecventei pattern[0..(lp-1)]
  int k, bestk = 0;

  urm[0] = 0;
  for(int i = 1; i < lp; i++) {
    k = urm[i - 1];
    while(k > 0 && pattern[i] != pattern[k])
      k = urm[k - 1];
    if(pattern[i] == pattern[k])
      k++;
    urm[i] = k;
  }
  return urm[lp - 1];
}

// afiseaza adn-ul care contine sirurile din bm si se termina in sirul cu indicele ind
// Complexitate: O(L * log(bm)), L = lungimea maxima a unui sir
void get_adn(int ind, int bm) {
  if(d[bm][ind][1] == -1)
    printf("%s", s[ind]);
  else {
    int old_bm = bm ^ (1 << ind), dad = d[bm][ind][1], old_l = d[old_bm][dad][0], crt_l = d[bm][ind][0], lind = l[ind];
    get_adn(dad, old_bm);
    printf("%s", s[ind] + lind - crt_l + old_l);
  }
}

// verifica daca sirul ind2 este o subsecventa a sirului ind1
// Complexitate: O(L1 + L2), L1 = l[ind1], L2 = l[ind2]
bool inside(int ind1, int ind2) {
  int l2 = l[ind2], l1 = l[ind1];
  int aux = prefix(s[ind2], l2); // calculez functia prefix pentru sirul ind2
  int k = 0; // k va indica lungimea celui mai mare prefix al lui ind2 care este sufix al lui s[ind1][0..i]
  // daca k = l2 inseamna ca am gasit o potrivire a lui s[ind2] in s[ind1]

  for(int i = 0; i < l1; i++) {
    while(k > 0 && s[ind1][i] != s[ind2][k])
      k = urm[k - 1];
    if(s[ind1][i] == s[ind2][k])
      k++;
    if(k == l2)
      return true;
  }
  return false;
}

// elimina sirul cu indicele ind
// Complexitate: O(N)
void elim(int ind) {
  for(int i = ind; i < n; i++)
    swap(s[i], s[i + 1]), swap(l[i], l[i + 1]);
  n--;
}

int main() {
  freopen("adn.in", "r", stdin);
  freopen("adn.out", "w", stdout);
  int pn; // pn = 2^n

  // citire
  scanf("%d ", &n);
  for(int i = 0; i < n; i++) {
    scanf("%s", &s[i]);
    l[i] = strlen(s[i]);
  }
  // elimin toate sirurile care sunt incluse complet in altele, pentru ca nu modifica lungimea raspunsului final
  // Complexitatea acestui pas: O(N^3)
  for(int i = 0; i < n; i++)
    for(int j = 0; j < n; j++)
      if(j != i && inside(i, j)) /// daca j se afla in interiorul lui i
        elim(j--);
  pn = 1 << n;

  // calculez matricea pref; pentru a calcula sufixul maxim a sirului j care este prefix al sirului i
  // este suficient sa concatenam cele 2 siruri, iar intre ele sa punem un caracter care nu apare in
  // niciun sir, de exemplu '#', pentru a nu exista suprapuneri intre cele 2 siruri, iar apoi sa rulam
  // functia prefix pe sirul astfel obtinut
  // Complexitatea acestui pas: O(N^2 * L), L = lungimea maxima a unui sir
  for(int i = 0; i < n; i++) {
    int li = l[i];
    strncpy(p, s[i], li);
    p[li] = '#';
    for(int j = 0; j < n; j++) {
      if(i == j)
        continue;
      int lj = l[j];
      strncpy(p + li + 1, s[j], lj);
      p[li + lj + 1] = '\0';
      pref[j][i] = prefix(p, li + lj + 1);
    }
  }

  // fac un lant hamiltonian format din siruri
  // Complexitatea acestui pas: O(N^2 * 2^N)
  for(int bm = 1; bm < pn; bm++)
    for(int i = 0; i < n; i++)
      if(bm & (1 << i)) {
        // voi calcula d[bm][i][0,1] uitandu-ma la ce am calculat deja
        int nbm = bm ^ (1 << i), li = l[i];

        if(nbm == 0) // in bitmask se afla doar indicele i
          d[bm][i][0] = li, d[bm][i][1] = -1;
        else
          for(int j = 0; j < n; j++) // presupun ca sirul j este cel dinaintea sirului i
            if(nbm & (1 << j)) { // j trebuie sa se afle in bitmask-ul din care l-am scos pe i
              int lj = l[j];
              // lungimea noului sir obtinut va fi lungimmea sirului care se termina in j + lungimea
              // sirului i, din care scadem ce au in comun sirul i si sirul j, adica pref[j][i]
              if(d[bm][i][0] == 0 || d[nbm][j][0] - pref[j][i] + li < d[bm][i][0]) {
                d[bm][i][0] = d[nbm][j][0] - pref[j][i] + li;
                d[bm][i][1] = j;
              }
            }
      }

  // calculez lungimea minima a sirului care contine toate sirurile
  int minim = 18 * LMAX + 5, pozmin = 0;
  for(int i = 0; i < n; i++)
    if(d[pn - 1][i][0] != 0 && d[pn - 1][i][0] < minim)
      minim = d[pn - 1][i][0], pozmin = i;
  // obtin sirul rezultat
  // Complexitatea acestui pas: O(L * N)
  get_adn(pozmin, pn - 1);

  return 0;
}