Cuestiones generales
La técnica de diseño de algoritmos llamada "divide y vencerás" (divide and conquer) consiste
en descomponer el problema original en varios sub-problemas más sencillos, para luego resolver
éstos mediante un cálculo sencillo. Por último, se combinan los resultados de cada sub-problema
para obtener la solución del problema original. El pseudocódigo sería:
funcion divide_y_venceras_1(problema)
{
descomponer el problema en n subproblemas más pequeños;
para i=1 hasta n hacer
resolver el subproblema k;
combinar las n soluciones;
}
Un ejemplo de "divide y vencerás" es la
ordenación rápida, o quicksort, utilizada para ordenar arrays. En ella, se dividía el array
en dos sub-arrays, para luego resolver cada uno por separado, y unirlos
(ver algoritmos de ordenación). El ahorro de tiempo es grande: el tiempo necesario para
ordenar un array de elementos mediante el método de la burbuja es cuadrático: kN2. Si
dividimos el array en dos y ordenamos cada uno de ellos, el tiempo necesario para resolverlo
es ahora k(N/2)2+k(N/2)2=(kN2)/2. El tiempo necesario para
ordenarlo es la mitad, pero sigue siendo cuadrático.
Pero ahora, si los subproblemas son todavía demasiado
grandes, ¿por qué no utilizar la misma táctica con ellos,
esto es, dividirlos a ellos también, utilizando un algoritmo recursivo
(ver recursividad) que vaya dividiendo más el
sub-problema hasta que su solución sea trivial? Un algoritmo del tipo:
funcion divide_y_venceras(problema)
{
si el problema es trivial
entonces resolver el problema;
si no es trivial
{
descomponer el problema en n subproblemas más pequeños;
para i=1 hasta n hacer
divide_y_venceras(subproblema_k);
combinar las n soluciones;
}
}
Si aplicamos este método al quicksort, el tiempo disminuye hasta ser
logarítmico, con lo que el tiempo ahorrado es mayor cuanto más aumenta N.
Tiempo de ejecución
El tiempo de ejecución de un algoritmo de divide y vencerás,
T(n), viene dado por la suma de dos elementos:
El tiempo que tarda en resolver los A subproblemas en los que se divide el original,
A·T(n/B), donde n/B es el tamaño de cada sub-problema.
El tiempo necesario para combinar las soluciones de los sub-problemas para hallar
la solución del original; normalmente es O(nk)
Por tanto, el tiempo total es: T(n) = A·T(n/B) + O(nk).
La solución de esta ecuación, si A es mayor o igual que 1 y B
es mayor que 1, es:
si A>Bk, T(n) = O(nlogBA)
si A=Bk, T(n) = O(nk·log n)
si A<Bk, T(n) = O(nk)
Determinación del umbral
Uno de los aspectos que hay que tener en cuenta en los algoritmos
de divide y vencerás es dónde colocar el umbral, esto es, cuándo
se considera que un sub-problema es suficientemente pequeño como para
no tener que dividirlo para resolverlo. Normalmente esto es lo que hace que
un algoritmo de divide y vencerás sea efectivo o no. Por ejemplo, en
el algoritmo de ordenación quicksort, cuando se tiene un array de longitud
3, es mejor ordenarlo utilizando otro algoritmo de ordenación (con 6
comparaciones se puede ordenar), ya que el quicksort debe dividirlo en dos sub-arrays
y ordenar cada uno de ellos, para lo que utiliza más de 6 comparaciones.
Problema de los puntos más cercanos
El problema es: "dado un conjunto de puntos P, hallar el par de puntos más
cercanos". La distancia entre dos puntos i y j es
sqrt[(xi-xj)2+(yi-yj)2]. Una
primera solución podría ser mirar todos los pares de puntos y quedarse con el más pequeño; al
haber n·(n-1)/2 pares de puntos, el tiempo de ejecución es de O(n2). El
programa resultante es muy corto, y rápido para casos pequeños, pero al ser este procedimiento
una búsqueda exhaustiva, debería haber un algoritmo más rápido.
Supongamos que ordenamos los puntos según la coordenada
x; esto supondría un tiempo de O(n·log n), lo que es una cota inferior
para el algoritmo completo. Ahora que se tiene el conjunto ordenado, se puede
trazar una línea vertical, x = xm, que divida al conjunto
de puntos en dos: Pi y Pd. Ahora, o el par más
cercano está en Pi, o está en Pd, o un punto
está en Pi y el otro en Pd. Si los dos estuvieran
en Pi o en Pd, se hallaría recursivamente, subdividiendo
más el problema, por lo que ahora el problema se reduce al tercer caso,
un punto en cada zona.
Llamemos di, dd y dc a las mínimas distancias
en el primer caso, en el segundo, y en el tercero, respectivamente, y dmin al menor
de di y dd. Para resolver el tercer caso, sólo hace falta mirar los
puntos cuya coordenada x esté entre xm-dmin y xm+dmin.
Para grandes conjuntos de puntos distribuidos uniformemente, el número de puntos que caen en esa
franja es sqrt(n), así que con una búsqueda exhaustiva el tiempo de ejecución sería de O(n), y
tendríamos el problema resuelto. El tiempo de ejecución sería, según lo dicho en el otro
apartado, O(n·log n).
Pero si los puntos no están uniformemente distribuidos,
la cosa cambia. En el peor de los casos, todos los puntos están en la
franja, así que la fuerza bruta no siempre funciona en tiempo lineal.
Para ello, se puede recurrir a ordenar los puntos de la franja según
la coordenada y, lo que supone un tiempo de ejecución de O(n·log n).
Si la coordenada y difiere en más de dmin, se puede pasar
al siguiente punto. El tiempo de ejecución es O(max(n·log n,n·log
n))=O(n·log n), con lo que mantenemos el tiempo de ejecución anterior.
El programa sería:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#define MAX 110L // Número máximo de puntos.
struct punto // Estructura de un punto.
{
double x;
double y;
};
void busca(struct punto *,int);
int ordenax(const void *,const void *);
double dist(struct punto,struct punto);
struct punto c1,c2; // Puntos más cercanos.
double mindist=1e10; // Mínima distancia.
int main()
{
int a,num;
double x,y;
struct punto p[MAX];
for(scanf(" %d",&num),a=0;a<num;a++) // Coge la entrada
{
scanf(" %lf %lf",&x,&y);
p[a].x=x;
p[a].y=y;
}
busca(p,num); // Hacer la primera búsqueda.
printf("Distancia minima: %.3lf\n",mindist);
printf("Puntos: (%.3lf,%.3lf) y (%.3lf,%.3lf)\n",c1.x,c1.y,c2.x,c2.y);
return(0);
}
void busca(struct punto *p,int num)
{
double d;
int desde,hasta,a,b;
if(num<=1) // Si no hay pares de puntos:
return; // salir.
// Ordenar los puntos por la coordenada x.
qsort(p,num,sizeof(struct punto),ordenax);
// Mirar en el subconjunto de la izquierda.
busca(p,num/2);
// Mirar en el subconjunto de la derecha.
busca(p+num/2,(num+1)/2);
// Hallar los límites del conjunto central.
for(desde=num/2; desde>0 && p[num/2].x-p[desde].x<mindist; desde--);
for(hasta=num/2; hasta<num-1 && p[hasta].x-p[num/2].x<mindist; hasta++);
// Búsqueda exhaustiva en el conjunto central.
for(a=desde;a<=hasta;a++)
for(b=a+1;b<=hasta;b++)
if((d=dist(p[a],p[b]))<mindist)
{
mindist=d;
c1.x=p[0].x;
c1.y=p[0].y;
c2.x=p[1].x;
c2.y=p[1].y;
}
}
// Función auxiliar del qsort.
int ordenax(const void *a,const void *b)
{
return(((*(struct punto *)a).x<(*(struct punto *)b).x)?-1:1);
}
// Función que calcula la distancia entre dos puntos.
double dist(struct punto a,struct punto b)
{
return(sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y)));
}
Hallar la mediana de un conjunto de puntos
La mediana es el elemento de un conjunto que cumple que en el conjunto hay el mismo
número de elemntos mayores que él y menores que él. Dicho de otra forma, si el conjunto está
ordenado, la mediana es el elemento central. Una forma de resolverlo es esa, ordenar el conjunto
y localizar el elemnto central. El tiempo de ejecución sería de O(n·log n), pero, ¿se podría resolver
en tiempo lineal utilizando una estrategia de divide y vencerás?
Hay un método llamado selección rápida que
tiene un tiempo esperado lineal, pero que en algunos casos puede tener tiempo
O(n2). Este método no sólo puede hallar la mediana,
sino el elemento que ocupa la posición k-ésima. Para ello, se
toma un elemento pivote (normalmente el primero), y se divide el conjuntos en
dos sub-conjuntos, según los elementos sean mayores o menores que el
pivote. El pivote ahora ocupará una posición p. Si p es igual
a k, el número buscado es el pivote; si p es menor que k, el número
buscado está en el primer subconjunto, y si p es mayor que k, el número
buscado está en el segundo subconjunto. Este método se continua
recursivamente, hasta que se halle el elemento buscado. El programa sería:
#include <stdio.h>
#include <stdlib.h>
int seleccionrapida(int *,int,int);
int k;
int main()
{
int num,a,m;
int *array;
scanf(" %d",&num); // Pedir el tamaño del array
array=(int *)malloc(sizeof(int)*num);
for(a=0;a<num;a++) // Dar valores al array
scanf(" %d",&array[a]);
scanf(" %d",&k); // Pedir el valor k
k--; // Por ir de 0 a num-1
m=seleccionrapida(array,0,num-1); // Para llamar a la función
printf("%d\n",m);
return(0);
}
int seleccionrapida(int *array,int desde,int hasta)
{
int i,d,aux; // i realiza la búsqueda de izquierda a derecha
// y j realiza la búsqueda de derecha a izquierda.
for(i=desde+1,d=hasta; ; ) // Valores iniciales de la búsqueda.
{
for( ;i<=hasta && array[i]<=array[desde]; i++); // Primera búsqueda
for( ;d>=0 && array[d]>=array[desde]; d--); // Segunda búsqueda
if(i<d) // si no se han cruzado:
{
aux=array[i]; // Intercambiar.
array[i]=array[d];
array[d]=aux;
}
else // si se han cruzado:
break; // salir del bucle.
}
if(d==desde-1) // Si la segunda búsqueda se sale del array
d=desde; // es que el pivote es el elemento
// más pequeño: se cambia con él mismo.
aux=array[d]; // Colocar el pivote
array[d]=array[desde]; // en su posición.
array[desde]=aux;
if(d==k)
return(array[d]); // El pivote es el elemento buscado
else if(d>k)
return(seleccionrapida(array,desde,d-1)); // Buscar en el primer array.
else
return(seleccionrapida(array,d+1,hasta)); // Buscar en el segundo array.
}
En esta solución en tiempo medio lineal se descartan sólo unos cuantos elementos
cada vez que se llama a la función recursiva, dependiendo del pivote que elijamos. Para
descartar una fracción constante de elementos (y mejorar el tiempo de ejecución del peor caso)
habría que elegir un mejor pivote, a ser preferible la mediana (cosa que no puede ser, porque es
justamente ella la que estamos buscando). Tampoco se puede perder mucho tiempo en buscar un buen
pivote, porque haría demasiado lento el programa. Una opción sería elegir tres elementos y
utilizar como pivote la mediana de esos tres, pero en el peor caso eso sigue siendo una mala
opción. Hay un algoritmo de elección del pivote llamado "partición con base en la mediana de la
mediana de cinco", que consiste en:
Dividir los n elementos en n/5 grupos de 5 elementos (ignorando los últimos elementos).
Encontrar la mediana de cada grupo de 5 elementos, lo que da una lista de n/5 medianas M.
Hallar la mediana de M, o un buen pivote en M, lo que se puede hacer utilizando este
algoritmo recursivamente.
Se puede probar que, utilizando este método, y en el peor de los casos, cada
llamada a la función recursiva desprecia a más del 30% de los elementos. Esto hace que el
algoritmo de selección rápida sea lineal, a pesar de que utilizamos un algoritmo auxiliar
para la búsqueda del pivote. El problema completo sería:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int seleccionrapida(int *,int *,int,int,int);
int hallamediana(int *,int *,int);
int main()
{
int num,a,m,k;
int *array,*pos;
scanf(" %d",&num); // Pedir el tamaño del array
array=(int *)malloc(sizeof(int)*num);
for(a=0;a<num;a++) // Dar valores al array
scanf(" %d",&array[a]);
scanf(" %d",&k); // Pedir el valor k
k--;
pos=(int *)malloc(sizeof(int)*num);
m=seleccionrapida(array,pos,0,num-1,k); // Para llamar a la función
printf("%d\n",m);
return(0);
}
int seleccionrapida(int *array,int *pos,int desde,int hasta,int k)
{
int i,d,aux; // i realiza la búsqueda de izquierda a derecha
// y j realiza la búsqueda de derecha a izquierda.
// Hallar la falsa mediana para tomarla como pivote
for(i=desde;i<=hasta;i++)
pos[i-desde]=i;
d=hallamediana(array+desde,pos,hasta-desde+1);
aux=array[d]; // Poner la falsa mediana al principio.
array[d]=array[desde];
array[desde]=aux;
for(i=desde+1,d=hasta;;) // Valores iniciales de la búsqueda.
{
for(;i<=hasta && array[i]<=array[desde];i++); // Primera búsqueda
for(;d>=0 && array[d]>=array[desde];d--); // segunda búsqueda
if(i<d) // si no se han cruzado:
{
aux=array[i]; // Intercambiar.
array[i]=array[d];
array[d]=aux;
}
else // si se han cruzado:
break; // salir del bucle.
}
if(d==desde-1) // Si la segunda búsqueda se sale del array
d=desde; // es que el pivote es el elemento
// más pequeño: se cambia con él mismo.
aux=array[d]; // Colocar el pivote
array[d]=array[desde]; // en su posición.
array[desde]=aux;
if(d==k)
return(array[d]); // El pivote es el elemento buscado
else if(d>k)
return(seleccionrapida(array,pos,desde,d-1,k)); // Buscar en el primer array.
else
return(seleccionrapida(array,pos,d+1,hasta,k)); // Buscar en el segundo array.
}
int hallamediana(int *array,int *pos,int num)
{
int a,*array2,b,c,d;
if(num<5) // Si hay menos de 5 elementos
return(pos[0]); // vale el primero (lo que devuelve esta
// función es el índice de la mediana en la variable "array" de la función
// seleccionrapida que llama por primera vez a esta función.
// Hallar la mediana de cada cinco elementos.
array2=(int *)malloc(sizeof(int)*((num/5)+4));
for(a=0;a+4<num;a+=5)
{
memcpy(array2+(a/5),array,sizeof(int)*5);
for(b=0;b<5;b++)
for(c=b+1;c<5;c++)
if(array2[b]>array2[c])
{
d=array2[b];
array2[b]=array2[c];
array2[c]=d;
d=pos[b];
pos[b]=pos[c];
pos[c]=d;
}
array2[a/5]=array[a+2];
pos[a/5]=pos[a+2];
}
// Hallar la mediana de las medianas.
a=hallamediana(array2,pos,num/5);
free(array2);
return(a);
}
Multiplicación de matrices
Dadas dos matrices A y B de tamaño n x n, hallar C, el producto de las dos.
Un primer algoritmo se saca de la definición de producto de matrices: Ci j es el
producto escalar de la i-ésima fila por la j-ésima columna. Su tiempo de ejecución sería
O(n3):
int i,j,k;
int A[n][n],B[n][n],C[n][n];
// Dar valores a A y B.
for(i=0;i<n;i++)
for(i=0;i<n;i++)
{
C[i][j]=0;
for(i=0;i<n;i++)
C[i][j]+=A[i][k]*B[k][j];
}
Pero este tiempo de ejecución también se puede mejorar. Si n es una potencia
de 2, se pueden dividir A, B y C en cuatro submatrices cada una, de la forma:
Ahora, para hallar las submatrices de C, basta con utilizar que:
C1,1 = A1,1·B1,1 + A1,2·B2,1
C1,2 = A1,1·B1,2 + A1,2·B2,2
C2,1 = A2,1·B1,1 + A2,2·B2,1
C2,2 = A2,1·B1,2 + A2,2·B2,2
Con este método el tiempo de ejecución es de T(n) = 8·T(n/2) + O(n2), o
lo que es lo mismo, O(n3), con lo que no habría diferencia entre este método y
el primero propuesto. Hay que reducir el número de multiplicaciones por debajo de 8. Esto
se logra utilizando el algoritmo de Strassen. Definimos las siguientes matrices:
M1 = (A1,2 - A2,2)·(B2,1
+ B2,2)
M2 = (A1,1 + A2,2)·(B1,1 + B2,2)
M3 = (A1,1 - A2,1)·(B1,1 + B2,1)
M4 = (A1,1 + A1,2)·B2,2
M5 = A1,1·(B1,2 - B2,2)
M6 = A2,2·(B2,1 - B1,1)
M7 = (A2,1 + A2,2)·B1,1
para las que se necesitan 7 multiplicaciones diferentes, y ahora se
calculan las submatrices de C utilizando sólo sumas:
C1,1 = M1 + M2 - M4 + M6
C1,2 = M4 + M5
C2,1 = M6 + M7
C2,2 = M2 - M3 + M5 - M7
El nuevo tiempo de ejecución es de T(n) = 7·T(n/2) + O(n2),
es decir, de T(n) = O(nlog27) = O(n2.81). Este
algoritmo sólo es mejor que el directo cuando n es muy grande, y es más
inestable cuando se utiliza con números reales, por lo que tiene una
aplicabilidad limitada.
Para el caso general en el que n no es potencia de 2, se pueden rellenar las matrices con ceros
hasta que las dos matrices sean cuadradas y n sea una potencia de 2.
El programa queda así:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int max(int,int);
int **multiplicamatriz(int **,int **,int); // Función recursiva que multiplica matrices
int **suma(int **,int **,int); // suma de matrices
int **resta(int **,int **,int);// Resta de matrices
void libera(int **,int); // Libera memoria
int main()
{
int **mapa1,**mapa2,**sol;
int f1,c1,f2,c2,a,b,m,m1;
scanf(" %d %d",&f1,&c1); // Tamaño de la primera matriz
scanf(" %d %d",&f2,&c2); // Tamaño de la segunda matriz
m1=max(f1,max(c1,max(f2,c2)));
for(m=1;m<m1;m*=2); // El tamaño de las matrices cuadradas a multiplicar
//debe ser de la forma 2k, si no se completan con ceros.
mapa1=(int **)malloc(sizeof(int *)*m); // Se crea la primera matriz
for(a=0;a<m;a++)
{
mapa1[a]=(int *)malloc(sizeof(int)*m);
memset(mapa1[a],0,sizeof(int)*m);
}
for(a=0;a<f1;a++) // Se cogen los datos de la primera matriz.
for(b=0;b<c1;b++)
scanf(" %d",&mapa1[a][b]);
mapa2=(int **)malloc(sizeof(int *)*m); // Se crea la sedunda matriz.
for(a=0;a<m;a++)
{
mapa2[a]=(int *)malloc(sizeof(int)*m);
memset(mapa2[a],0,sizeof(int)*m);
}
for(a=0;a<f2;a++) // Se cogen los datos de la segunda matriz.
for(b=0;b<c2;b++)
scanf(" %d",&mapa2[a][b]);
sol=multiplicamatriz(mapa1,mapa2,m); // Se multiplican.
for(a=0;a<f1;a++) // Se imprime el resultado.
{
for(b=0;b<c2;b++)
printf("%d ",sol[a][b]);
printf("\n");
}
return(0);
}
int max(int a,int b)
{
return((a>b)?a:b);
}
int **multiplicamatriz(int **mapa1,int **mapa2,int num)
{
int **sol,**M[8],**f1,**f2,**aux,**aux2;
int **A[2][2],**B[2][2],**C[2][2];
int a,q,w,r;
sol=(int **)malloc(sizeof(int *)*num);
for(a=0;a<num;a++)
sol[a]=(int *)malloc(sizeof(int)*num);
if(num==1)
{
sol[0][0]=mapa1[0][0]*mapa2[0][0];
return(sol);
}
// Crear las submatrices de A y B.
for(q=0;q<2;q++)
{
for(w=0;w<2;w++)
{
A[q][w]=(int **)malloc(sizeof(int *)*(num/2));
for(a=0;a<num/2;a++)
{
A[q][w][a]=(int *)malloc(sizeof(int)*(num/2));
for(r=0;r<num/2;r++)
A[q][w][a][r]=mapa1[a+(num/2)*q][r+(num/2)*w];
}
B[q][w]=(int **)malloc(sizeof(int *)*(num/2));
for(a=0;a<num/2;a++)
{
B[q][w][a]=(int *)malloc(sizeof(int)*(num/2));
for(r=0;r<num/2;r++)
B[q][w][a][r]=mapa2[a+(num/2)*q][r+(num/2)*w];
}
}
}
// Hallar las matrices M.
f1=resta(A[0][1],A[1][1],num/2);
f2=suma(B[1][0],B[1][1],num/2);
M[1]=multiplicamatriz(f1,f2,num/2);
libera(f1,num/2);
libera(f2,num/2);
f1=suma(A[0][0],A[1][1],num/2);
f2=suma(B[0][0],B[1][1],num/2);
M[2]=multiplicamatriz(f1,f2,num/2);
libera(f1,num/2);
libera(f2,num/2);
f1=resta(A[0][0],A[1][0],num/2);
f2=suma(B[0][0],B[0][1],num/2);
M[3]=multiplicamatriz(f1,f2,num/2);
libera(f1,num/2);
libera(f2,num/2);
f1=suma(A[0][0],A[0][1],num/2);
f2=B[1][1];
M[4]=multiplicamatriz(f1,f2,num/2);
libera(f1,num/2);
f1=A[1][1];
f2=resta(B[0][1],B[1][1],num/2);
M[5]=multiplicamatriz(f1,f2,num/2);
libera(f2,num/2);
f1=A[1][1];
f2=resta(B[1][0],B[0][0],num/2);
M[6]=multiplicamatriz(f1,f2,num/2);
libera(f2,num/2);
f1=suma(A[1][0],A[1][1],num/2);
f2=B[0][0];
M[7]=multiplicamatriz(f1,f2,num/2);
libera(f1,num/2);
// Hallar las submatrices de C.
C[0][0]=suma(M[1],M[2],num/2);
aux=C[0][0];
C[0][0]=resta(C[0][0],M[4],num/2);
aux2=C[0][0];
C[0][0]=suma(C[0][0],M[6],num/2);
libera(aux,num/2);
libera(aux2,num/2);
C[0][1]=suma(M[4],M[5],num/2);
C[1][0]=suma(M[6],M[7],num/2);
C[1][1]=resta(M[2],M[3],num/2);
aux=C[1][1];
C[1][1]=suma(C[1][1],M[5],num/2);
aux2=C[1][1];
C[1][1]=resta(C[1][1],M[7],num/2);
libera(aux,num/2);
libera(aux2,num/2);
for(a=1;a<=7;a++)
libera(M[a],num/2);
// Unir las submatrices de matrices C en sol.
for(q=0;q<num;q++)
for(w=0;w<num;w++)
sol[q][w]=C[q/(num/2)][w/(num/2)][q%(num/2)][w%(num/2)];
// Liberar las submatrices de A, B y C.
for(q=0;q<2;q++)
for(w=0;w<2;w++)
{
libera(A[q][w],num/2);
libera(B[q][w],num/2);
libera(C[q][w],num/2);
}
return(sol);
}
int **suma(int **mapa1,int **mapa2,int num)
{ // sumar mapa1 y mapa2.
int a,b;
int **sol;
sol=(int **)malloc(sizeof(int *)*num);
for(a=0;a<num;a++)
{
sol[a]=(int *)malloc(sizeof(int)*num);
for(b=0;b<num;b++)
sol[a][b]=mapa1[a][b]+mapa2[a][b];
}
return(sol);
}
int **resta(int **mapa1,int **mapa2,int num)
{ // Restar mapa2 de mapa1.
int **sol;
int a,b;
sol=(int **)malloc(sizeof(int *)*num);
for(a=0;a<num;a++)
{
sol[a]=(int *)malloc(sizeof(int)*num);
for(b=0;b<num;b++)
sol[a][b]=mapa1[a][b]-mapa2[a][b];
}
return(sol);
}
void libera(int **mapa,int num)
{
int a;
for(a=0;a<num;a++) // Liberar la tabla dinámica de 2D.
free(mapa[a]);
free(mapa);
}
Multiplicación de dos enteros
Este problema apareció en la OIE'2000 y su solución
más efectiva es la que utiliza la técnica "divide y vencerás".
El tamaño no es lo importante: Enunciado
y Solución.
Ficheros
Multiplicación de matrices en C/C++: multmatrices.cpp |