Matchings
Explicación
Queremos colocar la imagen más pequeña (imagen del robot) sobre la imagen más grande (imagen del suelo) de modo que se maximice el número de píxeles coincidentes.
Sea la imagen del robot (máscara) una grilla , y la imagen del suelo una grilla , donde y .
Para una colocación fija , contamos:
Como los valores son solo o , separamos esto en dos contribuciones independientes:
- posiciones donde ambos son
- posiciones donde ambos son
Definimos arreglos indicadores:
Entonces las coincidencias de son:
Para contar las coincidencias de , observamos:
Así las coincidencias totales quedan:
Computamos ambos términos por separado y los sumamos.
¿Se puede resolver con una sola FFT?
En lugar de computar dos convoluciones separadas (una para coincidencias de s y otra para coincidencias de s), podemos reducir el problema a una sola convolución.
Transformamos los arreglos así:
- reemplazar
- dejar
Ahora consideremos la contribución de una sola posición:
- si los píxeles coinciden, obtenemos o
- si no coinciden, obtenemos
Así la convolución computa directamente:
También sabemos:
Combinando ambos,
Así una sola convolución basta para recuperar el número de píxeles coincidentes.
Convertir a multiplicación de polinomios
Reducimos el problema 2D a 1D usando aplanado por filas (row-major).
Mapeamos la posición al índice:
Construimos arreglos:
- Máscara aplanada →
a - Suelo aplanado →
b
Para simular correctamente el deslizamiento, revertimos el arreglo a.
Ahora la contribución:
se convierte en una convolución estándar.
Después de la multiplicación, cada índice del arreglo resultante corresponde a un desplazamiento específico. Para una colocación , el índice correspondiente es:
Este valor da el número de píxeles coincidentes para esta colocación.
Repetimos el mismo proceso para y para contar los píxeles coincidentes, y sumamos los resultados:
Iteramos sobre todas las colocaciones válidas y nos quedamos con las que alcanzan el valor máximo.
Optimizar con FFT
Hacerlo por fuerza bruta corre en tiempo .
Con la Transformada Rápida de Fourier (FFT), la convolución corre en tiempo , donde .
¿Se puede reducir el tamaño de la FFT?
En la multiplicación de polinomios estándar, elegimos el tamaño de la FFT de al menos para evitar el solapamiento de la convolución circular.
Sin embargo, en este problema solo nos importan los desplazamientos en los que la máscara queda completamente dentro de la imagen del suelo.
El desplazamiento válido máximo se queda dentro del rango de índices que consultamos, que está acotado por , así que no necesitamos el rango completo de la convolución.
Así, basta elegir el tamaño de la FFT como la menor potencia de tal que:
en lugar de .
Esto reduce tanto el uso de memoria como el tiempo de ejecución aproximadamente a la mitad.
Implementación
Complejidad temporal: donde
#include <bits/stdc++.h>
using namespace std;
using cd = complex<double>;
const double PI = acos(-1);
// BeginCodeSnip{FFT Template}
void fft(vector<cd> &a, bool inv) {
int n = a.size();
if (n == 1) return;
vector<cd> a0(n / 2), a1(n / 2);
for (int i = 0; 2 * i < n; i++) a0[i] = a[2 * i], a1[i] = a[2 * i + 1];
fft(a0, inv);
fft(a1, inv);
double ang = 2 * PI / n * (inv ? -1 : 1);
cd w(1), wn(cos(ang), sin(ang));
for (int i = 0; 2 * i < n; i++) {
a[i] = a0[i] + w * a1[i];
a[i + n / 2] = a0[i] - w * a1[i];
if (inv) a[i] /= 2, a[i + n / 2] /= 2;
w *= wn;
}
}
vector<int> mul(vector<int> a, vector<int> b) {
int n = 1;
while (n < (int)a.size() + (int)b.size()) n <<= 1;
vector<cd> fa(a.begin(), a.end()), fb(b.begin(), b.end());
fa.resize(n);
fb.resize(n);
fft(fa, 0);
fft(fb, 0);
for (int i = 0; i < n; i++) fa[i] *= fb[i];
fft(fa, 1);
vector<int> r(n);
for (int i = 0; i < n; i++) r[i] = round(fa[i].real());
return r;
}
// EndCodeSnip
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
int m, n;
cin >> m >> n;
vector<vector<int>> mask(n, vector<int>(m));
for (int i = 0; i < n; ++i)
for (int j = 0; j < m; ++j) cin >> mask[i][j];
int m1, n1;
cin >> m1 >> n1;
vector<int> a((n - 1) * m1 + m, 0), b(n1 * m1);
for (int i = 0; i < n1; i++)
for (int j = 0; j < m1; j++) cin >> b[i * m1 + j];
for (int i = 0; i < n; i++)
for (int j = 0; j < m; j++) a[i * m1 + j] = mask[i][j];
reverse(a.begin(), a.end());
auto c1 = mul(a, b);
for (int i = 0; i < n; i++)
for (int j = 0; j < m; j++) a[i * m1 + j] = 1 - mask[i][j];
reverse(a.begin(), a.end());
for (int &x : b) x = 1 - x;
auto c2 = mul(a, b);
vector<int> c(c1.size());
for (int i = 0; i < (int)c.size(); i++) c[i] = c1[i] + c2[i];
int best = 0;
for (int y = 0; y <= n1 - n; y++) {
for (int x = 0; x <= m1 - m; x++) {
int ind = y * m1 + x;
int i = ind + (int)a.size() - 1;
best = max(best, c[i]);
}
}
vector<pair<int, int>> ans;
for (int y = 0; y <= n1 - n; y++) {
for (int x = 0; x <= m1 - m; x++) {
int ind = y * m1 + x;
int i = ind + (int)a.size() - 1;
if (c[i] == best) ans.push_back({x, y});
}
}
sort(ans.begin(), ans.end());
for (auto &p : ans) cout << p.first << " " << p.second << '\n';
}