Skip to Content

Stamp Painting

Análisis oficial (C++) 

Solución

Esta es una solución similar a la del editorial oficial pero usa un Árbol de Segmentos para las transiciones. Recomiendo encarecidamente leer el editorial oficial además de esta solución.

Observemos las restricciones necesarias para que un ordenamiento de sellos sea válido. Debe haber al menos un segmento de longitud k o mayor. Podemos trabajar hacia atrás empezando desde este segmento. Quitamos este segmento de longitud kk. Luego podemos quitar los segmentos a su izquierda y a su derecha porque, aunque no sean más largos que kk, podemos asumir que se superpusieron con el segmento anterior que quitamos. Podemos quitar toda la pintura de esta forma.

Ahora solo necesitamos contar el número de ordenamientos con un segmento contiguo de longitud kk con el mismo color. En vez de esto, podemos usar conteo complementario y contar lo contrario con programación dinámica. Sea dp[i]dp[i] el número de ordenamientos para los primeros ii caracteres. Cuando ii es menor que kk, dp[i]=midp[i] = m^i ya que los primeros ii caracteres pueden ser cualquiera de los mm colores. Observemos que podemos agregar un segmento de longitud de hasta k1k - 1 de un solo color al final de un ordenamiento válido. Este segmento puede tener m1m - 1 colores (no podemos elegir el color que corresponde al segmento anterior porque los dos segmentos combinados pueden volverse más largos que kk). Así, podemos enunciar las siguientes recurrencias.

Si x<kx < k, entonces dp[x]dp[x] será mxm^x. En caso contrario, si x>=kx >= k, entonces dp[x]dp[x] será la suma de los últimos k1k - 1 estados de dpdp multiplicada por m1m - 1. Más formalmente, podemos definir esta relación como:

dp[x]=mx if x<kdp[x] = m^x \texttt{ if } x < k dp[x]=(m1)i=xk+1x1dp[i] if xkdp[x] = (m - 1) \cdot \sum_{i=x-k+1}^{x-1} dp[i] \texttt{ if } x \ge k

Para calcular el segundo tipo de transición, podemos usar un Árbol de Segmentos. Podemos restar dp[n]dp[n] de mnm^n (número total de pinturas) para obtener el número de pinturas válidas.

Implementación

Complejidad temporal: O(Nlog(N))\mathcal{O}(N \log(N))

#include <bits/stdc++.h> using namespace std; using ll = long long; const ll MOD = 1e9 + 7; // BeginCodeSnip{Segment Tree} template <class T> struct SegTree { const T ID = 0; T cmb(T a, T b) { return (a + b) % MOD; } int n; vector<T> seg; SegTree(int _n) { for (n = 1; n < _n;) n *= 2; seg.assign(2 * n, ID); } void pull(int p) { seg[p] = cmb(seg[2 * p], seg[2 * p + 1]); } void upd(int p, T val) { seg[p += n] = val; for (p /= 2; p; p /= 2) pull(p); } T query(int l, int r) { T ra = ID, rb = ID; for (l += n, r += n + 1; l < r; l /= 2, r /= 2) { if (l & 1) ra = cmb(ra, seg[l++]); if (r & 1) rb = cmb(seg[--r], rb); } return cmb(ra, rb); } }; // EndCodeSnip /** * Binary exponentiation for fast exponentiation. * However, since n is small enough, you could also precalculate the powers of m * instead. */ ll bi_pow(ll a, ll b) { ll ans = 1; while (b) { if (b & 1) { ans = ans * a % MOD; } a = a * a % MOD; b /= 2; } return ans; } int main() { freopen("spainting.in", "r", stdin); freopen("spainting.out", "w", stdout); ll n, m, k; cin >> n >> m >> k; // Initialize segment tree SegTree<ll> dp(n + 1); for (int i = 1; i <= n; i++) { ll a; if (i < k) { // If i is less than k, dp[i] will be a power of m a = bi_pow(m, i); } else { // Otherwise, apply the second dp formula a = (m - 1) * dp.query(max(1, i - (int)k + 1), i - 1) % MOD; } dp.upd(i, a); } // Subtract dp[n] from the total number of segments cout << (bi_pow(m, n) + MOD - dp.query(n, n)) % MOD << endl; }