nilpot.cpp

// $Id$

/* COMIENZO DE DESCRIPCION 

   __USE_WIKI__
   Dada una correspondencia #M# tal que el 
   conjunto de sus valores es igual al conjunto 
   de sus claves, encontrar el \'i{}ndice 
   nilpotente, de la misma, es decir el n\'umero de 
   veces #n# que hay que componerla consigo misma 
   hasta llegar a la identidad, es decir #M^n = I#. 
   _Consigna:_  Escribir una funci\'on
   #int nilpot(map<int,int> &M);# que dada una correspondencia
   #M# retorna el m\'\i{}nimo entero \verb+n+ tal que #M^n=I#.
   [Tomado en el 1er parcial 21/4/2005]. 
   keywords: correspondencia

   FIN DE DESCRIPCION */
// -----------------------------------------------------------------
/* Dadas dos correspondencias #M_1# y #M_2# la _composici\'on_ 
  de ambas es la correspondencia
  #M = M_2 . M_1# tal que si #M_1[a]=b# y #M_2[b]=c#, entonces
  #M[a]=c#. Por ejemplo, si 
  #M1={(0,1),(1,2),(2,0),(3,4),(4,3)}#, y 
  #M2={(0,1),(1,0),(2,3),(3,4),(4,2)}#, entonces
  #M = M_1 . M_2 ={(0,0),(1,3),(2,1),(3,2),(4,4)}#.
  Notemos que para que sea posible componer las dos correspondencias
  es necesario que los valores del contradominio de #M_1# est\'en
  incluidos en las claves de #M_2#. Si el conjunto de valores del
  contradominio de una correspondencia #M# est\'a incluido en el 
  conjunto de sus claves, entonces podemos componer a #M# consigo 
  misma, es decir, #M^2 = M . M#. Por ejemplo, 
  #M_1^2 = M_1 . M_1 = {(0,2),(1,0),(2,1),(3,3),(4,4)}#. 
  De la misma manera puede definirse, #M^3,...,M^n#, componiendo 
  sucesivamente. Puede demostrarse que, para alg\'un #n#, debe ser 
  #M^n=I#, donde #I# es la correspondencia identidad_ , es decir 
  aquella tal que #I[x]=x#. Por ejemplo, si 
  #M = {(0,1),(1,2),(2,0)}#, entonces para #n=3#, #M^n=M^3=I#.
  _Consigna:_  Escribir una funci\'on
  #int nilpot(map<int,int> &M);# que dada una correspondencia
  #M# retorna el m\'\i{}nimo entero \verb+n+ tal que #M^n=I#.
  _Sugerencia:_ Escribir dos funciones auxiliares:
  #void compose(map<int,int> &M1,map<int,int> &M2,map<int,int> &M);# 
  que dadas dos correspondencias #M1, M2#, calcula la composici\'on
  #M = M_2 . M_1#, devolvi\'endola en el argumento #M#,
  #bool is_identity(map<int,int> &M);#
  que dada una correspondencia #M#, retorna #true# si
  #M# es la identidad, y #false# en caso contrario.  */
// -----------------------------------------------------------------
#include <time.h>
#include <sys/time.h>
#include <cassert>
#include <iostream>
#include <vector>
#include <map>
#include <algorithm>

using namespace std ;

// -------------------------------------------------------------------
typedef map<int,int> map_t;
typedef pair<int,int> pair_t;

// -----------------------------------------------------------------
// Obtiene el tiempo en segundos. 
// warning: `gettimeofday' is a GNU extension. 
double gettod() {
 struct timeval tv;
 gettimeofday (&tv,0);
 return tv.tv_sec + 1e-6 * tv.tv_usec;
}

// -----------------------------------------------------------------
// Imprime la correspondencia
void print_map(map_t &M) {
  map_t::iterator q = M.begin();
  while (q!=M.end()) {
    cout << q->first << " -> " << q->second << endl;
    q++;
  }
}
// -----------------------------------------------------------------
// Compone dos correspondencias 
void compose_map(map_t &M1,
		 map_t &M2,
		 map_t &M) {
  map_t::iterator q = M1.begin();
  while (q!=M1.end()) {
    M[q->first] = M2[q->second];
    q++;
  }
}

// -----------------------------------------------------------------
// Predicado que verifica si una correspondencia es la identidad. 
bool is_identity(map_t &M) {
  map_t::iterator q = M.begin();
  while (q!=M.end()) {
    if (q->first!=q->second) return false;
    q++;
  }
  return true;
}

// -----------------------------------------------------------------
// Verifica cual potencia de M es la identidad (indice nilpotente). 
int nilpot(map<int,int> &M) {
  map_t Mj=M, Maux;
  int n=1;
  while (!is_identity(Mj)) {
    compose_map(M,Mj,Maux);
    Mj = Maux;
    n++;
  }
  return n;
}

// -----------------------------------------------------------------
// Calcula el maximo comum divisor de x e y.
int gcd(int x, int y) {
  int a = x, b = y;
  if (b>a) {
    a = y; b = x;
  }
  while (true) {
    int c = a % b;
    if (!c) return b;
    a = b; b = c;
  }
}

// -----------------------------------------------------------------
// Calcula el minimo comun multiplo de x e y. 
int mcm(int x, int y) {
  return x*y/gcd(x,y);
}

// -------------------------------------------------------------------
// Construye una correspondencia aleatoria como una permutacion. 
void rand_map(map_t &M,int n) {
  // Genera un vector con los elementos 0 a n-1. 
  vector<int> v(n);
  for (int j=0; j<n; j++) v[j] = j;
  // Reordena aleatoriamente usando el
  // algoritmo #random_shuffle# de STL. 
  random_shuffle(v.begin(),v.end());
  // Asigna en la correspondencia el valor
  // #v[j]# a la clave #j#. 
  M.clear();
  for (int j=0; j<n; j++) 
    M[j] = v[j];
}

// -------------------------------------------------------------------
// Esta implementacion es mucho mas eficiente. Para cada una de las 
// claves `k' se calcula su propio indice nilpotente `n(k)' que es 
// el numero de veces que hay que aplicarle `M' para volver a `k'. 
// 
// Por ejemplo, si #M={(0,1),(1,2),(2,0),(3,4),(4,3)}#, entonces
// el indice `n(0)=3' ya que aplicandole la correspondencia a 0 
// obtenemos sucesivamente `0->1->2->0', es decir que debemos 
// aplicarla 3 veces para llegar de nuevo a 0. De la misma forma 
// tenemos `n(1)=3, n(2)=3, n(3)=2, n(4)=2'. El indice nilpotente 
// de `M' es el minimo comun multiplo del indice nilpotente de todas 
// sus claves. Por ejemplo en el caso anterior tenemos 
// #nilpot(M) = mcm(3,3,3,2,2) = 6#.
// 
// Esta implementacion es mucho mas eficiente que la anterior. Si el 
// numero de elementos es #n# entonces el indice nilpotente de #M# 
// puede crecer tanto como el numero de permutaciones de #n#
// elementos, que es #n!#. Como el algoritmo anterior revisa todas 
// las potencias de #M#, puede llegar a ser #O(n.n!)#.  El factor 
// #n# viene del hecho de que para cada potencia #M^j# hacer la
// composicion y chequear para ver si es la identidad es #O(n)#. Por 
// el contrario, el indice nilpotente de cada clave puede ser a lo 
// sumo #n#, de manera que calcular el indice nilpotente de todas las 
// claves puede ser a lo sumo #O(n^2)#. Luego calcular el MCM de todos 
// los indices (usando el algoritmo de Euclides, es a lo sumo 
// #O(n log(n))#. De manera que para el algoritmo rapido tenemos a 
// lo sumo #O(n^2)#.
// -------------------------------------------------------------------
template<class T>
int nilpot2(map<T,T> &M) {
  map_t::iterator q = M.begin();
  int n=1;
  while (q!=M.end()) {
    int c = 1;
    T x = q->first, y=q->second;
    while (y!=x) {
      y = M[y]; c++;
    }
    n = mcm(n,c);
    q++;
  }
  return n;
}

// -------------------------------------------------------------------
int main() {
  map_t M;
  int kaso = 1 ;
  int n ;
  if       (kaso == 1) {
    M.insert(pair_t(0,1));
    M.insert(pair_t(1,2));
    M.insert(pair_t(2,3));
    M.insert(pair_t(3,4));
    M.insert(pair_t(4,0));
    M.insert(pair_t(5,6));
    M.insert(pair_t(6,5));
    print_map (M);
    n = nilpot (M);
    cout << "indice nilpotente ; n = " << n << endl ; 
  } else if (kaso == 2) {
    double t=0.0, t2=0.0;
    int count=0;
    while (true) {
      count++;
      // Genera correspondencias (que son
      // permutaciones) aleatoriamente.
      rand_map(M,20);
      print_map(M);
  
      // Obtiene el indice nilpotente "n" con los dos metodos. 
      // Reporta los tiempos para los dos metodos. 
      double s,elap,elap2; 
      int n2;
      s = gettod();
      // n = nilpot(M);
      elap = gettod()-s;
      t += elap;
  
      s = gettod();
      n2 = nilpot2(M);
      elap2 = gettod()-s;
      t2 += elap2;
      // assert(n==n2);
      printf("nilpot(M): %d, elaps %f,%f, averg: %f,%f\n",
  	   n2,elap,elap2,t/count,t2/count);
    }
  } // end if

  double t=0., t2=0.;
  int count=0;
  while (true) {
    count++;
    // Genera correspondencias (que son
    // permutaciones) aleatoriamente.
    rand_map(M,20);
    // print_map(M);

    // Obtiene el indice nilpotente
    // con los dos metodos. Reporta
    // los tiempos para los dos metodos. 
    double s,elap,elap2; int n,n2;
    s = gettod();
    // n = nilpot(M);
    elap = gettod()-s;
    t += elap;

    s = gettod();
    n2 = nilpot2(M);
    elap2 = gettod()-s;
    t2 += elap2;
    // assert(n==n2);

    printf("nilpot(M): %d, elaps %f,%f, averg: %f,%f\n",
	   n2,elap,elap2,t/count,t2/count);
  }

  cout << endl ;
  return 0 ;
}
// -----------------------------------------------------------------

Generated by GNU Enscript 1.6.6.