Be Geeks!#9760

Be Geeks! (Editoriale)

Il numero di coppie è pari a n \cdot (n + 1) / 2 e per n = 2 \cdot 10^5 è circa 2 \cdot 10^{10}, quindi enumerare tutti i segmenti è impossibile. Calcoliamo la somma con il metodo "divide et impera", suddividendo l'array nella posizione dell'elemento massimo.

Suddivisione nel massimo. Supponiamo di dover trovare la somma di P(i, j) su tutti i segmenti contenuti nel segmento . Troviamo la posizione dell'elemento massimo su . Ogni segmento [i; j] \subseteq [b; e] appartiene esattamente a uno dei tre tipi:

I segmenti del secondo e del terzo tipo vengono elaborati ricorsivamente. Per i segmenti del primo tipo il massimo è pari ad a_m, quindi M(i, j) = a_m, e il loro contributo totale è pari a

a_m \cdot \sum G(i, j), \quad \text{dove la somma è presa su tutti i } b \le i \le m \le j \le e

Resta da calcolare rapidamente la somma dei MCD su tutti i segmenti che contengono la posizione .

Scomposizione del MCD. Un segmento che contiene è formato dalla parte sinistra e dalla parte destra . Pertanto

G(i, j) = \gcd\bigl(G(i, m), G(m, j)\bigr)

Quindi il MCD di un segmento è determinato da una coppia di valori: il MCD della parte sinistra e il MCD della parte destra.

Gruppi di estremi. Al crescere di il valore G(m, j) non aumenta. Inoltre, ogni volta che cambia, il nuovo valore è un divisore proprio del precedente ed è quindi almeno due volte più piccolo. Di conseguenza G(m, j) assume al più \lfloor \log_2 C \rfloor + 1 valori distinti, dove è il valore massimo di un elemento. Per C \le 10^9 si tratta di al più \lfloor \log_2 C \rfloor + 1 = 30 valori.

Le posizioni con lo stesso valore di G(m, j) formano un blocco contiguo. Chiamiamo un tale blocco di posizioni gruppo. Ogni gruppo è descritto da una coppia (g, \mathit{cnt}), dove è il valore del MCD e è il numero di posizioni nel gruppo. Analogamente, gli estremi sinistri vengono suddivisi in gruppi in base al valore di .

La fine di un gruppo si trova con la ricerca binaria. Se un gruppo inizia nella posizione e g = G(m, j), allora l'uguaglianza G(m, t) = g vale per tutti i da fino alla fine del gruppo e non vale per tutti i successivi.

Somma sulle coppie di gruppi. Sia (g_L, \mathit{cnt}_L) il gruppo sinistro e (g_R, \mathit{cnt}_R) il gruppo destro. Allora tutti i \mathit{cnt}_L \cdot \mathit{cnt}_R segmenti il cui estremo sinistro appartiene al gruppo sinistro e il cui estremo destro appartiene al gruppo destro hanno lo stesso MCD \gcd(g_L, g_R). Pertanto

\sum G(i, j) = \sum \gcd(g_L, g_R) \cdot \mathit{cnt}_L \cdot \mathit{cnt}_R,

dove la seconda somma è presa su tutte le coppie formate da un gruppo sinistro e un gruppo destro. Il numero di tali coppie non supera 30 \cdot 30.

Query su intervallo. Per trovare la posizione del massimo e per calcolare il MCD su un segmento usiamo due sparse table. Entrambe le operazioni sono idempotenti: \max(x, x) = x e \gcd(x, x) = x. Pertanto una query su intervallo si risolve con due blocchi sovrapposti di lunghezza 2^k.

Il valore di è molto grande, quindi tutti i calcoli vengono eseguiti modulo 10^9 + 7.

Complessità. Ogni chiamata ricorsiva esclude dalla considerazione una posizione , quindi le chiamate sono al più . In ogni chiamata ci sono al più O(\log C) gruppi per ciascun lato. La fine di un gruppo si trova con una ricerca binaria che esegue O(\log n) query del MCD, e ogni query richiede O(\log C). L'iterazione sulle coppie di gruppi richiede O(\log^2 C) calcoli del MCD. La costruzione delle sparse table richiede O(n \log n \cdot \log C). La complessità complessiva è O(n \log^2 C \cdot (\log n + \log C)). In pratica i gruppi sono molto meno di e l'algoritmo è veloce.

Esempio

Consideriamo il secondo esempio: A = (2, 4, 6, 12, 3). Le posizioni sono numerate a partire da uno.

La prima chiamata elabora l'intero array . L'elemento massimo è a_4 = 12, quindi m = 4. La figura mostra i gruppi degli estremi sinistri e destri.

Estremi sinistri: G(4, 4) = 12, G(3, 4) = \gcd(6, 12) = 6, G(2, 4) = G(1, 4) = 2. Otteniamo i gruppi , e .

Estremi destri: G(4, 4) = 12, G(4, 5) = \gcd(12, 3) = 3. Otteniamo i gruppi e .

Esaminiamo tutte le coppie di gruppi:

gruppo sinistro

gruppo destro

MCD

segmenti

contributo

(12, 1)

(12, 1)

12

1

12

(12, 1)

(3, 1)

3

1

3

(6, 1)

(12, 1)

6

1

6

(6, 1)

(3, 1)

3

1

3

(2, 2)

(12, 1)

2

2

4

(2, 2)

(3, 1)

1

2

2

La somma dei MCD sui segmenti che contengono la posizione è 12 + 3 + 6 + 3 + 4 + 2 = 30, e il contributo di questi segmenti è 12 \cdot 30 = 360. Ad esempio, l'ultima riga della tabella corrisponde ai segmenti e : per ciascuno di essi il MCD è 1 e il massimo è 12.

Successivamente vengono elaborati ricorsivamente i segmenti e . Tutte le chiamate sono riportate nella tabella:

segmento

\boldsymbol{a_m}

gruppi sinistri

gruppi destri

somma MCD

contributo

\boldsymbol{[1; 5]}

4

12

(12, 1), (6, 1), (2, 2)

(12, 1), (3, 1)

30

360

\boldsymbol{[1; 3]}

3

6

(6, 1), (2, 2)

(6, 1)

10

60

\boldsymbol{[1; 2]}

2

4

(4, 1), (2, 1)

(4, 1)

6

24

\boldsymbol{[1; 1]}

1

2

(2, 1)

(2, 1)

2

4

\boldsymbol{[5; 5]}

5

3

(3, 1)

(3, 1)

3

9

La risposta è 360 + 60 + 24 + 4 + 9 = 457.

Implementazione dell'algoritmo

Dichiariamo le costanti:

Dichiariamo il tipo ll.

#define MOD 1000000007LL
#define LOG 18 // 2^18 = 262144 > 2*10^5
typedef long long ll;

La sequenza è memorizzata nell'array ; gli elementi sono indicizzati a partire da zero. Il valore è pari a \lfloor \log_2 \mathit{len} \rfloor. La sparse table \mathit{spMax}[i][k] memorizza l'indice dell'elemento massimo, mentre \mathit{spGcd}[i][k] memorizza il MCD degli elementi sul segmento [i; i + 2^k - 1].

vector<int> a;
vector<int> lg2;          // lg2[len] = floor(log2(len))
// spMax[i][k] — indice del massimo su [i, i + 2^k)
vector<vector<int>> spMax;
// spGcd[i][k] — MCD su [i, i + 2^k)
vector<vector<int>> spGcd;

La funzione gcd calcola il MCD di due numeri con l'algoritmo di Euclide.

int gcd(int x, int y)
{
  while (y) { int t = x % y; x = y; y = t; }
  return x;
}

La funzione better, dati due indici e , restituisce quello il cui elemento dell'array è maggiore. Se gli elementi sono uguali, restituisce .

int better(int i, int j)
{
  return a[i] >= a[j] ? i : j;
}

La funzione build riempie l'array e costruisce entrambe le sparse table in O(n \log n \cdot \log C), C \le 10^9.

void build()
{
  lg2.assign(n + 1, 0);
  for (int len = 2; len <= n; len++) lg2[len] = lg2[len / 2] + 1;
  spMax.assign(n, vector<int>(LOG + 1));
  spGcd.assign(n, vector<int>(LOG + 1));
  for (int i = 0; i < n; i++)
  {
    spMax[i][0] = i;
    spGcd[i][0] = a[i];
  }
  for (int k = 1; (1 << k) <= n; k++)
    for (int i = 0; i + (1 << k) <= n; i++)
    {
      int h = 1 << (k - 1);    // metà della lunghezza del blocco
      spMax[i][k] = better(spMax[i][k - 1], spMax[i + h][k - 1]);
      spGcd[i][k] = gcd(spGcd[i][k - 1], spGcd[i + h][k - 1]);
    }
}

La funzione qMax restituisce l'indice dell'elemento massimo sul segmento [l; r].

int qMax(int l, int r)
{
  int k = lg2[r - l + 1];
  return better(spMax[l][k], spMax[r - (1 << k) + 1][k]);
}

La funzione qGcd restituisce il MCD degli elementi sul segmento [l; r]. I blocchi di lunghezza 2^k possono sovrapporsi, ma ciò non influisce sul risultato, poiché \gcd(x, x) = x.

int qGcd(int l, int r)
{
  int k = lg2[r - l + 1];
  return gcd(spGcd[l][k], spGcd[r - (1 << k) + 1][k]);
}

La funzione lastRight trova la posizione più a destra t \in [\mathit{from}; e] tale che G(m, t) = g. Qui g = G(m, \mathit{from}). Poiché non aumenta al crescere di , l'uguaglianza vale su una parte iniziale del segmento , e la ricerca binaria ne trova l'ultima posizione. Il punto medio si calcola come (\mathit{lo} + \mathit{hi} + 1) / 2, affinché la ricerca non si blocchi sull'assegnazione \mathit{lo} = \mathit{mid}.

int lastRight(int m, int from, int e, int g)
{
  int lo = from, hi = e;
  while (lo < hi)
  {
    int mid = (lo + hi + 1) / 2;
    if (qGcd(m, mid) == g) lo = mid; else hi = mid - 1;
  }
  return lo;
}

La funzione firstLeft trova in modo simmetrico la posizione più a sinistra t \in [b; \mathit{from}] tale che G(t, m) = g.

int firstLeft(int m, int from, int b, int g)
{
  int lo = b, hi = from;
  while (lo < hi)
  {
    int mid = (lo + hi) / 2;
    if (qGcd(mid, m) == g) hi = mid; else lo = mid + 1;
  }
  return lo;
}

La struttura Group descrive un gruppo di estremi dei segmenti:

struct Group { int g; ll cnt; };

La funzione solve restituisce, modulo , la somma di P(i, j) su tutti i segmenti contenuti in . Per un segmento vuoto la somma è .

ll solve(int b, int e)
{
  if (b > e) return 0;

Troviamo la posizione dell'elemento massimo su .

  int m = qMax(b, e);

Suddividiamo gli estremi destri j = m, m + 1, \ldots, e in gruppi. Per il corrente calcoliamo g = G(m, j) e con la funzione lastRight troviamo l'ultima posizione del gruppo. Il gruppo contiene t - j + 1 posizioni; il gruppo successivo inizia nella posizione t + 1.

  vector<Group> R;
  for (int j = m; j <= e; )
  {
    int g = qGcd(m, j);
    int t = lastRight(m, j, e, g);
    R.push_back({ g, t - j + 1 });
    j = t + 1;
  }

Analogamente suddividiamo in gruppi gli estremi sinistri i = m, m - 1, \ldots, b. Il gruppo occupa le posizioni da a e contiene i - t + 1 posizioni; il gruppo successivo termina nella posizione t - 1.

  vector<Group> L;
  for (int i = m; i >= b; )
  {
    int g = qGcd(i, m);
    int t = firstLeft(m, i, b, g);
    L.push_back({ g, i - t + 1 });
    i = t - 1;
  }

Calcoliamo la somma dei valori del MCD su tutti i segmenti che contengono la posizione . Per ogni coppia di gruppi aggiungiamo \gcd(l.g, r.g) \cdot l.\mathit{cnt} \cdot r.\mathit{cnt}. Il prodotto l.\mathit{cnt} \cdot r.\mathit{cnt} non supera 4 \cdot 10^{10} e rientra nel tipo long long. Lo riduciamo modulo ; dopodiché anche il prodotto del MCD (minore di 2^{30}) per un numero minore di rientra in long long.

  ll sumG = 0;
  for (Group &l : L)
    for (Group &r : R)
    {
      ll cnt = (l.cnt * r.cnt) % MOD;
      sumG = (sumG + gcd(l.g, r.g) % MOD * cnt) % MOD;
    }

Il massimo su tutti i segmenti che contengono è pari ad a_m, quindi il loro contributo totale è a_m \cdot \mathit{sumG}.

  ll res = (ll)a[m] % MOD * sumG % MOD;

Aggiungiamo le somme calcolate ricorsivamente per i segmenti situati interamente a sinistra e a destra della posizione .

  return (res + solve(b, m - 1) + solve(m + 1, e)) % MOD;
}

Parte principale del programma. Leggiamo i dati di input.

scanf("%d", &n);
a.resize(n);
for (int &x : a) scanf("%d", &x);

Costruiamo le sparse table.

build();

Stampiamo la risposta per l'intero array — il segmento [0; n - 1].

printf("%lld\n", solve(0, n - 1));