Pair Programming
Explicación
Sea la -ésima instrucción de Bessie y la -ésima instrucción de Elsie. Además, sea el conjunto de expresiones únicas que podemos formar intercalando las primeras instrucciones de Bessie y las primeras instrucciones de Elsie. Por ejemplo, en el sample:
3
12+
+02- : (intercalar y )
- : (intercalar y )
- : (intercalar y )
- : (intercalar y )
Entonces, ¿cómo calculamos ?
Cuando o son 0, el conjunto de instrucciones de Bessie o el de Elsie estará vacío, lo que significa que el intercalado está unívocamente determinado y por lo tanto para todo .
Bien, ¿pero qué pasa si y son ambos distintos de cero? Evidentemente, cualquier intercalado de las primeras instrucciones de Bessie y las primeras de Elsie debe terminar o bien con la instrucción o bien con la instrucción . Así podemos reescribir las expresiones que terminan con como (denotemos este conjunto como ) y las que terminan con como (denotemos este conjunto como ). También tenemos lo siguiente:
- Mientras no sea ,
- Mientras no sea ,
¡Tómese un tiempo para convencerse de esto, especialmente de los puntos 1 y 2!
En este punto, uno puede verse tentado a concluir que
¡No cometamos este error! Recordemos el principio de inclusión-exclusión:
Por ejemplo, si y , sobrecontamos si simplemente concatenamos estos dos conjuntos, así que hay que restar , que en este caso es .
En nuestro caso, sobrecontamos . Entonces, ¿qué expresiones están en este conjunto? Esta es una parte crucial de la solución, ¡así que hay que tomarse un tiempo para pensarlo antes de leer la respuesta de abajo! Para facilitar esto, se puede asumir primero que ni Bessie ni Elsie tienen la instrucción , ya que no es demasiado difícil ver si contiene la expresión y reduce el número de casos borde que hay que considerar.
Pista
¡Pensemos en la propiedad conmutativa !
Explicación detallada
Nota: Esta explicación presenta la solución de forma muy abstracta, ¡pero debería ser bastante intuitiva!
Consideremos las propiedades de una expresión después de aplicarle una instrucción:
- : todos los coeficientes deben ser múltiplos de
- : el coeficiente de x es exactamente Llamemos a estos los rasgos de esa instrucción, y digamos que una instrucción X es violada por una instrucción posterior Y si, después de aplicar la instrucción Y, los rasgos de la instrucción X ya no se cumplen. Como ejemplo, sea la instrucción X y la instrucción Y . Después de aplicar la instrucción X a la expresión , obtenemos , que satisface el rasgo de que todos los coeficientes deben ser múltiplos de 2. Sin embargo, después de aplicar la instrucción Y, obtenemos , una expresión cuyos coeficientes ya no son todos pares; por lo tanto, la instrucción Y viola la instrucción X. De estas definiciones, obtenemos un hecho importante: dos operaciones que no se violan entre sí deben ser conmutativas (¡intentemos ver la intuición)!
Si una expresión está contenida en ambos conjuntos y , por definición se puede escribir tanto como como , y por lo tanto debe satisfacer los rasgos de y de . Así, tenemos que:
- y son conmutativas
- la expresión se puede escribir como (o ). Sin embargo, como esta expresión aún debe estar en , vemos que debe estar en , ya que ya colocamos tanto como al final de la secuencia de instrucciones. Por lo tanto, tenemos que cuando y son conmutativas, y en caso contrario.
TL;DR
Si y son operaciones conmutativas (es decir, hace lo mismo que ), sobrecontamos el conjunto . ¡Hay que recordar también el caso borde de la expresión (más detallado en el código más adelante)!
Implementación
Complejidad temporal:
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 2e3 + 1;
const int MOD = 1e9 + 7;
// suma y resta módulo
ll ad(ll &a, ll b) { return a = (a + b) % MOD; }
ll sb(ll &a, ll b) { return a = (((a - b) % MOD) + MOD) % MOD; }
// devuelve si c es un dígito de 1 a 9
bool digit(char c) { return '1' <= c && c <= '9'; }
int main() {
int test_num;
cin >> test_num;
for (int t = 0; t < test_num; t++) {
int n;
cin >> n;
vector<char> a(n + 1), b(n + 1);
for (int i = 1; i <= n; i++) { cin >> a[i]; }
for (int i = 1; i <= n; i++) { cin >> b[i]; }
// zero[i][j] -> si 0 está en I(i, j)
vector<vector<bool>> zero(n + 1, vector<bool>(n + 1));
// I(0, 0) = {0}
zero[0][0] = true;
for (int i = 0; i <= n; i++) {
for (int j = 0; j <= n; j++) {
if (!i && !j) continue;
// I(i, j) contiene 0 si:
// 1. a[i] o b[j] = 0
// 2. I(i - 1, j) contiene 0 y a[i] no es +x
// 3. I(i, j - 1) contiene 0 y b[j] no es +x
zero[i][j] = a[i] == '0' || b[j] == '0' ||
i && zero[i - 1][j] && a[i] != '+' ||
j && zero[i][j - 1] && b[j] != '+';
}
}
// dp[i][j] -> número de expresiones no nulas en I(i, j)
vector<vector<ll>> dp(n + 1, vector<ll>(n + 1));
dp[0][0] = 0;
// |I(i, 0)| = |I(0, i)| = 1 ->
// # de expresiones no nulas = 1 - # de expresiones nulas
for (int i = 1; i <= n; i++) {
dp[0][i] = !zero[0][i];
dp[i][0] = !zero[i][0];
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
dp[i][j] = 0;
if (a[i] != '0') { ad(dp[i][j], dp[i - 1][j]); }
if (a[i] == '+') { ad(dp[i][j], zero[i - 1][j]); }
if (b[j] != '0') { ad(dp[i][j], dp[i][j - 1]); }
if (b[j] == '+') { ad(dp[i][j], zero[i][j - 1]); }
// restamos la intersección de A y B
if (a[i] == '+' && b[j] == '+' || a[i] == '1' && b[j] == '+' ||
a[i] == '+' && b[j] == '1') {
sb(dp[i][j], dp[i - 1][j - 1] + zero[i - 1][j - 1]);
} else if (digit(a[i]) && digit(b[j])) {
sb(dp[i][j], dp[i - 1][j - 1]);
}
}
}
cout << ad(dp[n][n], zero[n][n]) << '\n';
}
}