Be Geeks!#9760

Be Geeks! (Editorial)

El número de pares es igual a n \cdot (n + 1) / 2, y para n = 2 \cdot 10^5 es aproximadamente 2 \cdot 10^{10}, por lo que enumerar todos los segmentos es imposible. Calcularemos la suma mediante el método "divide y vencerás", dividiendo el arreglo en la posición del elemento máximo.

División en el máximo. Supongamos que necesitamos hallar la suma de P(i, j) sobre todos los segmentos contenidos en el segmento . Hallemos la posición del elemento máximo en . Cada segmento [i; j] \subseteq [b; e] pertenece exactamente a uno de tres tipos:

Los segmentos del segundo y del tercer tipo se procesan recursivamente. Para los segmentos del primer tipo el máximo es igual a a_m, por lo que M(i, j) = a_m, y su contribución total es igual a

a_m \cdot \sum G(i, j), \quad \text{donde la suma se toma sobre todos los } b \le i \le m \le j \le e

Queda calcular rápidamente la suma de los MCD sobre todos los segmentos que contienen la posición .

Descomposición del MCD. Un segmento que contiene a está formado por la parte izquierda y la parte derecha . Por lo tanto

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

Así, el MCD de un segmento queda determinado por un par de valores: el MCD de la parte izquierda y el MCD de la parte derecha.

Grupos de extremos. A medida que aumenta, el valor G(m, j) no aumenta. Además, cada vez que cambia, el nuevo valor es un divisor propio del anterior y, por lo tanto, es al menos dos veces menor. En consecuencia, G(m, j) toma como máximo \lfloor \log_2 C \rfloor + 1 valores distintos, donde es el mayor valor de un elemento. Para C \le 10^9 esto son como máximo \lfloor \log_2 C \rfloor + 1 = 30 valores.

Las posiciones con el mismo valor de G(m, j) forman un bloque contiguo. Llamaremos a un bloque de posiciones así grupo. Cada grupo se describe mediante un par (g, \mathit{cnt}), donde es el valor del MCD y es el número de posiciones del grupo. De manera análoga, los extremos izquierdos se dividen en grupos según el valor de .

El final de un grupo se encuentra mediante búsqueda binaria. Si un grupo empieza en la posición y g = G(m, j), entonces la igualdad G(m, t) = g se cumple para todos los desde hasta el final del grupo y no se cumple para ningún posterior.

Suma sobre pares de grupos. Sea (g_L, \mathit{cnt}_L) el grupo izquierdo y (g_R, \mathit{cnt}_R) el grupo derecho. Entonces todos los \mathit{cnt}_L \cdot \mathit{cnt}_R segmentos cuyo extremo izquierdo está en el grupo izquierdo y cuyo extremo derecho está en el grupo derecho tienen el mismo MCD \gcd(g_L, g_R). Por lo tanto

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

donde la segunda suma se toma sobre todos los pares formados por un grupo izquierdo y un grupo derecho. El número de tales pares no supera 30 \cdot 30.

Consultas en rangos. Para hallar la posición del máximo y calcular el MCD en un segmento, usamos dos tablas dispersas (Sparse Table). Ambas operaciones son idempotentes: \max(x, x) = x y \gcd(x, x) = x. Por lo tanto, una consulta en un rango se responde usando dos bloques superpuestos de longitud 2^k.

El valor de es muy grande, por lo que todos los cálculos se realizan módulo 10^9 + 7.

Complejidad. Cada llamada recursiva excluye una posición , por lo que hay como máximo llamadas. En cada llamada hay como máximo O(\log C) grupos a cada lado. El final de un grupo se encuentra mediante búsqueda binaria con O(\log n) consultas de MCD, y cada consulta requiere O(\log C). Recorrer los pares de grupos requiere O(\log^2 C) cálculos de MCD. La construcción de las tablas dispersas requiere O(n \log n \cdot \log C). La complejidad total es O(n \log^2 C \cdot (\log n + \log C)). En la práctica hay muchos menos de grupos y el algoritmo funciona rápidamente.

Ejemplo

Consideremos el segundo ejemplo: A = (2, 4, 6, 12, 3). Las posiciones se numeran a partir de uno.

La primera llamada procesa todo el arreglo . El elemento máximo es a_4 = 12, por lo que m = 4. La figura muestra los grupos de extremos izquierdos y derechos.

Extremos izquierdos: G(4, 4) = 12, G(3, 4) = \gcd(6, 12) = 6, G(2, 4) = G(1, 4) = 2. Obtenemos los grupos , y .

Extremos derechos: G(4, 4) = 12, G(4, 5) = \gcd(12, 3) = 3. Obtenemos los grupos y .

Recorramos todos los pares de grupos:

grupo izquierdo

grupo derecho

MCD

segmentos

contribución

(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 suma de los MCD sobre los segmentos que contienen la posición es 12 + 3 + 6 + 3 + 4 + 2 = 30, y la contribución de estos segmentos es 12 \cdot 30 = 360. Por ejemplo, la última fila de la tabla corresponde a los segmentos y : para cada uno de ellos el MCD es 1 y el máximo es 12.

A continuación se procesan recursivamente los segmentos y . Todas las llamadas se muestran en la tabla:

segmento

\boldsymbol{a_m}

grupos izquierdos

grupos derechos

suma de MCD

contribución

\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 respuesta es 360 + 60 + 24 + 4 + 9 = 457.

Implementación del algoritmo

Declaramos las constantes:

Declaramos el tipo ll.

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

La secuencia se almacena en el arreglo ; los elementos se indexan desde cero. El valor es igual a \lfloor \log_2 \mathit{len} \rfloor. La tabla dispersa \mathit{spMax}[i][k] almacena el índice del elemento máximo, y \mathit{spGcd}[i][k] almacena el MCD de los elementos del segmento [i; i + 2^k - 1].

vector<int> a;
vector<int> lg2;          // lg2[len] = floor(log2(len))
// spMax[i][k] — índice del máximo en [i, i + 2^k)
vector<vector<int>> spMax;
// spGcd[i][k] — MCD en [i, i + 2^k)
vector<vector<int>> spGcd;

La función gcd calcula el MCD de dos números mediante el algoritmo de Euclides.

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

La función better, dados dos índices y , devuelve aquel cuyo elemento del arreglo es mayor. Si los elementos son iguales, devuelve .

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

La función build rellena el arreglo y construye ambas tablas dispersas en 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);    // mitad de la longitud del bloque
      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 función qMax devuelve el índice del elemento máximo en el 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 función qGcd devuelve el MCD de los elementos del segmento [l; r]. Los bloques de longitud 2^k pueden superponerse, pero esto no afecta al resultado, ya que \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 función lastRight encuentra la posición más a la derecha t \in [\mathit{from}; e] tal que G(m, t) = g. Aquí g = G(m, \mathit{from}). Como no aumenta al crecer , la igualdad se cumple en una parte inicial del segmento , y la búsqueda binaria encuentra su última posición. El punto medio se calcula como (\mathit{lo} + \mathit{hi} + 1) / 2 para que la búsqueda no se quede atascada en la asignación \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 función firstLeft encuentra de forma simétrica la posición más a la izquierda t \in [b; \mathit{from}] tal que 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 estructura Group describe un grupo de extremos de segmentos:

struct Group { int g; ll cnt; };

La función solve devuelve, módulo , la suma de P(i, j) sobre todos los segmentos contenidos en . Para un segmento vacío la suma es .

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

Hallamos la posición del elemento máximo en .

  int m = qMax(b, e);

Dividimos los extremos derechos j = m, m + 1, \ldots, e en grupos. Para el actual calculamos g = G(m, j) y con la función lastRight encontramos la última posición del grupo. El grupo contiene t - j + 1 posiciones; el siguiente grupo empieza en la posición 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;
  }

De manera análoga, dividimos en grupos los extremos izquierdos i = m, m - 1, \ldots, b. El grupo ocupa las posiciones de a y contiene i - t + 1 posiciones; el siguiente grupo termina en la posición 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;
  }

Calculamos la suma de los valores del MCD sobre todos los segmentos que contienen la posición . Para cada par de grupos sumamos \gcd(l.g, r.g) \cdot l.\mathit{cnt} \cdot r.\mathit{cnt}. El producto l.\mathit{cnt} \cdot r.\mathit{cnt} no supera 4 \cdot 10^{10} y cabe en el tipo long long. Lo reducimos módulo ; después de eso, el producto del MCD (menor que 2^{30}) por un número menor que también cabe en 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;
    }

El máximo en todos los segmentos que contienen a es igual a a_m, por lo que su contribución total es igual a a_m \cdot \mathit{sumG}.

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

Sumamos las sumas calculadas recursivamente para los segmentos situados por completo a la izquierda y a la derecha de la posición .

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

Parte principal del programa. Leemos los datos de entrada.

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

Construimos las tablas dispersas.

build();

Imprimimos la respuesta para todo el arreglo — el segmento [0; n - 1].

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