AQP TRAINING #6 – 2011 finalizado!

Posted: December 19, 2010 in AQP Contest!

Holas a todos AQP Training #6 ah finalizado!! queremos agradecer de antemano a nuestro problemsetter Walter Erquínigo por haber preparado este contest y a todos los que participaron. Tenemos unas palabras de Walter:

Los resultados fueron mejor de lo que esperaba, felicito a TheGoldenEagle que ganó brutalmente en ambas divisiones. A Digberth y a Ziklon por haber hecho también 5 problemas quedando en segundo y tercer puesto de Div2. En Div1 felicito también a jpenam por haber hecho 4 problemas quedando segundo y a Jhosimar por quedar tercero con 3 problemas. Espero que les haya gustado el problem set y me esmeré buscando problemas que enseñen cosas importantes.🙂

A continuación presentaremos el analisis otorgado por Walter para ambas divisiones:

DIVISION 2

Stamps:

Problema bien simple: un greedy en el que primero ordenamos de mayor a menor y vemos el menor índice tal que la suma acumulada hasta dicho índice es mayor o igual a lo que queremos. En caso contrario, retornamos imposible.

Box of Bricks:

Otro problema simple pero que requiere pensar algo. Una vez que sabemos el número total de fichas F y el número de pilas P, ya sabemos cuántas fichas habrá finalmente en cada fila  T = F / P. Luego, revisamos cada pila p, que tiene X[p] fichas. Si X[p] < T fichas, entonces entrarán a él T – X[p] fichas; en caso contrario, saldrán de él X[p] – T fichas. Como cada ficha que sale de una pila tiene que entrar a otra pila, sabemos que la sumatoria  de T – X[p], para toda pila p que recibe fichas, es igual a la sumatoria de X[p] – T, para toda pila p que entrega fichas. Por lo tanto, la respuesta es la sumatoria de los X[p] – T, o la sumatoria de los  T – X[p].

Friends:

Problema básico e importante, es simplemente un DFS para contar en número de componentes conexos en un grafo, donde los vértices son las personas y las aristas son las relaciones de amistad. Incluyo un código:

#include<cstring>
#include<vector>
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;

vector<int> E[101];
bool seen[101];

void dfs(int u){
   if(seen[u]) return;
   seen[u] = true;
   for(int i = 0; i < E[u].size(); i++)
       dfs(E[u][i]);
}

int main(){
   int t; cin >> t;
   while(t--){
       int n, m;
       cin >> n >> m;
       for(int i = 0; i < n; i++) E[i].resize(0);
       while(m--){
           int a, b; scanf("%d %d", &a, &b);
           a--; b--;
           E[a].push_back(b); E[b].push_back(a);
       }
       int res = 0;
       memset(seen, 0, sizeof seen);
       for(int i = 0; i < n; i++) if(!seen[i]) dfs(i), res++;
       cout << res << endl;
   }
}

 
Bitwise reverse:

Primero se debe hallar el bit con mayor peso, y a partir de él, hacer un reverse de todos los otros bits de menor peso. Incluyo código.

#include<cstring>
#include<vector>
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;

int main(){

    int n;
    while(scanf("%d", &n) == 1 && n){
           int last;
           for(int i = 0; i < 30; i++)
           if(n & (1 << i)) last = i; //hallamos el bit más pesado que esté prendido
           int res = 0;

           for(int i = last, j = 0; i >= j; i--, j++){
                  int bi = (n & (1 << i)) != 0; //booleano [0, 1] que representa el valor del bit
                  int bj = (n & (1 << j)) != 0;
                  res |= bj << i;
                  res |= bi << j;
           }
           cout << res << endl;
    }
}
Marbles in Three Baskets:

Este tal vez es el problema más difícil de este Div2. Es un bfs con vértices de 3 dimensiones.
Normalmente en un bfs un vértice es representado por un número, teniendo así 1 dimensión; sin embargo, en este caso, un vértice es representado por 3 números (los contenidos en los 3 recipientes), teniendo así 3 dimensiones. Por lo tanto, para trabajar con un “arreglo” de distancias para el bfs, tendremos un arreglo de 3 dimensiones:

int distancias[N][N][N];
Y nuestra cola será un
queue<node> q;
donde node puede ser
struct node{
   int x, y, z;
   node(){}
};

Y dentro del bfs, como no podemos tener una lista o matriz de adyacencia, simulamos los envíos de contenido de un recipiente a otro, creando así otros estados válidos de recipientes. Esto es lo mismo que movernos por las aristas del grafo asociado, sólo que de manera implícita.

Y para hacer más fastidioso el proceso, tenemos que guardar una matriz de padres para reconstruir el camino. Añado el código por su importancia en el aprendizaje, dado que este tipo de problemas de caminos es frecuente

#include<set>
#include<cstring>
#include<iostream>
#include<vector>
#include<algorithm>
#include<cstdio>
#include<queue>
using namespace std;
struct node{
    int a[3];
    node(int x, int y, int z){
         a[0] = x;
         a[1] = y;
         a[2] = z;
    }
    node(){}
};

int main(){
    int a, b, c;
    int dist[60][60][60];
    node parent[60][60][60];
    while(scanf("%d %d %d", &a, &b, &c) == 3){
          memset(dist, -1, sizeof dist);
          dist[a][b][c] = 0;
          queue<node> cola;
          node begin(a, b, c);
          cola.push(begin);
          bool listo = false;
          int fin = (a + b + c) / 3;

          if((a + b + c) % 3 == 0) //si existe solución
          while(!cola.empty()){
               node cur = cola.front();
               cola.pop();
               bool sirve = true;
               for(int i = 0; i < 3; i++) if(cur.a[i] != fin) sirve = false;
                   if(sirve){
                       listo = true; break;
                   }

               for(int i = 0; i < 3; i++) for(int j = 0; j < 3; j++) if(i != j && cur.a[i] >= cur.a[j]){
                   node next = cur;
                   next.a[i] -= cur.a[j];
                   next.a[j] += cur.a[j];
                   if(dist[next.a[0]][next.a[1]][next.a[2]] == -1){
                        dist[next.a[0]][next.a[1]][next.a[2]] = dist[cur.a[0]][cur.a[1]][cur.a[2]] + 1;
                        parent[next.a[0]][next.a[1]][next.a[2]] = cur;
                        cola.push(next);
                   }
               }
          }

          if(!listo){
              printf("%4d%4d%4d\n", a, b, c);
              printf("============\n");
          }else{
              node cur(fin, fin, fin);
              vector<node> pasos(1, cur);
              while(true){
                   if(cur.a[0] == a && cur.a[1] == b && cur.a[2] == c) break;
                   pasos.push_back(parent[cur.a[0]][cur.a[1]][cur.a[2]]);
                   cur = parent[cur.a[0]][cur.a[1]][cur.a[2]];
              }
              for(int i = pasos.size() - 1; i >= 0; i--)
              printf("%4d%4d%4d\n", pasos[i].a[0], pasos[i].a[1], pasos[i].a[2]);
              printf("============\n");
         }
    }
}
Super Square:

Junto con los problemas del DFS y del BFS son los problemas más importantes de este Div2.
El problema pide calcular n ^ n (mod 2006), o en otras palabras n^n % 2006.

Primero vale la pena mencionar dos importantes propiedas de teoría de números

(a + b) % mod = (a % mod  +  b % mod) % mod
(a * b) % mod  = (a % mod  *  b % mod) % mod

Osea, cuando tenemos que hacer operaciones de suma o producto, y al resultado tenemos que sacarle módulo, podemos primero sacarle módulo a los operandos y luego al resultado, de esta manera, siempre podemos trabajar con número pequeños, puesto que n ^ n es grande, pero n ^ n % 2006 es máximo 2005.

n puede ser hasta 10^8, por lo que hacer esto

long long res = 1; //uso long long porque soy paranoico cada vez que veo números grandes
 for(int i = 0; i < n; i++)
      res = res * n % 2006;

a pesar de ser correcto esto puede ser muy lento, dándonos así un TLE.

Un mejor análisis nos permite descubrir esto

a^(2n) = (a^n) ^ 2, dado que 2n es par
a^(2n+1) = a * a^(2n)  = a * (a^n)^2,  dado que 2n+1 es impar

Entonces, podemos hacer esto

typedef long long ll;
ll expmod(ll a, ll e){
   if(e == 0) return 1;
   ll x = expmod(a, e / 2);
   x = x * x % 2006;
   if(e % 2 == 1) x = x * a % 2006;
   return x;
}

Llamado fast modular exponentiation. Es un algoritmo logarítmico (porque el exponente decrece sucesivamente por un factor de 2), que halla lo que queremos. No es muy difícil verificar la correctitud del algoritmo. Este es un algoritmo IMPORTANTE para problemas de matemática.

DIVISION 1

Finding Nemo:

Problema sencillo, sólo se necesitan huevos porque a primera vista da miedo. Lo primero es describir el grafo asociado, para esto tendremos:

int dx[] = {0, 0, 1, -1};
int dy[] = {1, -1, 0, 0};
bool E[202][202][4];

Las dos primeras ĺíneas nos indican los diferencias de movimiento que haremos dentro de nuestro algoritmo de búsqueda. La tercera línea nos dice, si para la posición (x, y) la dirección (d), asociada a (dx[d], dy[d]), está libre o tiene un muro. De esta manera, cada que vez recibamos del input una pared, simplemente marcamos con false los E[x][y][d] respectivos.

Luego tenemos que asignar a la posición final objetivo (a, b), que tiene coordenadas decimales, una posición en el grid con coordenadas enteras. Con un poco de análisis y haciendo ejemplos en papel, se verifica que dicha posición final es (floor(a), floor(b)).

Finalmente, una vez descrito el grafo y hallado la casilla objetivo del grid, se procede a hacer un dijkstra o un bfs modificado para pesos 0-1. 

How many 0’s?:

Este problema tiene dos soluciones, la primera es un dp un poco largo y la segunda es un greedy. Explicaré el greedy.
Primero es importante notar la periodicidad de los números. Si sólo nos fijamos en el último dígito de todo número notamos:

0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 …
Y si ahora nos fijamos en los segundos dígitos tenemos:
0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 …

Así, podemos hacer unos calculos con división y resto, para determinar el número de veces que aparece cada dígito en cada posición (primera posición, segunda, tercera …).
Nada muy complicado, sólo pensar un poco.

Mondriaan’s Dream:

Un DP clásico de nivel alto.
Definimos la recursión

f(mask, row, col) = suma de f(nmask, row, col + 1), para todo posible nmask
f(mask, n, 0) = mask == 0;

Tal que mask es lo siguiente:

                col
                [x] xxxxxxxx
row  xxxxx

Mask es un conjunto de bits, 1 o 0, tal que dice si un bloque está lleno o vacío. Ahora, para todas las columnas menores que col, dichas posiciones de la máscara indicarán el estado de dichas columnas en la fila row. Y para las columnas mayores a col, indicarán el estado de dichas columnas en la fila row – 1. Inicialmente considerando que la fila -1 es 0.

Pongo mi código:

#include<iostream>
#include<vector>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
int n, m;
ll dp[12][12][1 << 12];
ll cuenta(int row, int col, int mask){
    if(col == m) return cuenta(row + 1, 0, mask);
    ll &res = dp[row][col][mask];
    if(row == n){
        if(mask == 0) return  1;
        else return  0;
    }

    if(res != -1) return res;

    if(mask & (1 << col)) return res = cuenta(row, col + 1, mask - (1 << col));

    res = 0;
    if(col + 1 != m) if((mask & (1 << (col + 1))) == 0) res = cuenta(row, col + 2, mask);
    res += cuenta(row, col + 1, mask | (1 << col));
    return res;
}

int main(){
    while(scanf("%d %d", &m, &n) == 2 && n + m){   
         memset(dp, -1, sizeof dp);   
         printf("%lld\n", cuenta(0, 0, 0));
    }
}

Este problema es de importancia vital.

Box of Bricks:

Explicado de Div2🙂

Seminar Room:

Un problema sencillo de 2-SAT. Haré primero un outline de lo que es 2-SAT.

Un problema de 2-SAT consiste en hallar una solución afirmativa para una ecuación de la siguiente forma:


(extraído de Wikipedia).

Así, 2-SAT se reduce a asignar valores true o false a cada X[i], de manera que la expresión total, la conjunción de disyunciones, nos de valor verdadero.
El algoritmo para esto son 2 pasos.

Paso 1: crear el grafo asociado

Notemos que (a or b) = (~a -> b) and (~b ->a)
Entonces, creamos dos vértices para cada variable, con lo que tendremos en el grafo un vértice para X[i] y un vértice para ~X[i], y según las disyunciones colocamos las aristas.

Ahora el problema se reduce a escoger una de dos opciones para cada variable. (X[i] = true, ~X[i] = false) o (X[i] = false, ~X[i] = true), de manera que en grafo nunca ocurra que true -> false. Si logramos asignar valores a todas las variables sin romper esa restricción, el problema ya está listo, porque cada arista será de la forma
true -> true
false -> true
false -> false
Y todas esas expresiones son válidas en la expresión matemática arriba.

Paso 2: verificar si existe solución

Ahora corremos un algoritmo de Strongly Connected Components y si para algún i, X[i] y ~X[i] están en el mismo componente, no existe solución; en caso contrario, sí existe solución.
Si X[i] y ~X[i] están en el mismo componente, entonces existe un camino de X[i] a ~X[i] y un camino de ~X[i] a X[i]. Por lo tanto, como uno de ellos debe ser true y el otro false, existe un camino true -> false, lo cual es inválido y no existe solución. Si no hay tal contradicción, siempre es posible hallar una solución, dado que, al comprimir cada SCC en un supervértice, en el DAG asociado, X[i] y ~X[i] están en componentes diferentes, y si existe un camino de ~X[i] a X[i], entonces ~X[i] = false y X[i] = true, o si existe un camino de X[i] a ~X[i], lo contrario. Y si no existe relación entre X[i] y ~X[i], se asocia arbitariamente, manteniendo siempre la restricción de que no puede haber un true -> false. Para un mejor algoritmo para hallar la solución, recomiendo esta lectura: http://www.oi.edu.pl/download/boi-2001.pdf (leer el primer problema y su solución).

Finalmente, resolvemos el problema:
Cada grupo tiene dos opciones de horario, llamemos a la primera X[i] y a la segunda ~X[i]. Luego hacemos una verificación todos contra todos entre grupos diferentes. Si un horario A de un grupo se interseca con un horario B de otro grupo sabemos que A y B no pueden estar juntos, entonces:

~(A and B)

Y por la ley de Morgan

~(A and B) = (~A or ~B)

Finalmente, cada restricción es una disyunción, y como todas las disyunciones tienen que satisfechas, tenemos una conjunción de disyunciones, lo que es 2-sat.
Finalmente, creamos el grafo asociado y verificamos los SCC dando la respuesta. Acá pongo mi código:

#include<map>
#include<string>
#include<set>
#include<cstring>
#include<iostream>
#include<vector>
#include<algorithm>
#include<cstdio>
#include<stack>
using namespace std;

vector<int> adj[2000];
int timer, times[2000], comps, comp[2000], instack[2000], n;
stack<int> s;

void addclause(int a, int b){
   adj[a^1].push_back(b);
   adj[b^1].push_back(a);
}

int dia[7];
int from[2000], to[2000];

int diff(char a, char b, char c){
   if(a == 'M') return dia[0];
   else if(b == 'U') return dia[1];
   else if(a == 'W') return dia[2];
   else if(b == 'H') return dia[3];
   else if(a == 'F') return dia[4];
   else if(b == 'A') return dia[5];
   else return dia[6];
}

void clear(){
   for(int i = 0; i < 2 * n; i++){
       instack[i] = false; times[i] = -1;
       adj[i].resize(0);
   }
   comps = timer = 0;
}

int scc(int u){
   int low = times[u] = timer++;
   instack[u] = true; s.push(u);
   for(int i = 0; i < adj[u].size(); i++){
       int v = adj[u][i];
       if(times[v] == -1) low = min(low, scc(v));
       else if(instack[v]) low = min(low, times[v]);
   }
   if(low == times[u]){
       int x;
       do{
          x = s.top(); s.pop(); instack[x] = false;
          comp[x] = comps;
       }while(x != u);
       comps++;
   }
   return low;
}

int main(){
    int t; cin >> t;
    dia[0] = 0;
    for(int i = 1; i < 7; i++) dia[i] = dia[i - 1] + 24 * 60;

    while(t--){
        cin >> n;
        for(int i = 0; i < n; i++){
            char c1, c2, c3; int m, h, tiempo;
            scanf(" %c%c%c:%d:%d", &c1, &c2, &c3, &h, &m);
            tiempo = diff(c1, c2, c3) + h * 60 + m;
            from[2 * i] = tiempo;
            scanf(" %c%c%c:%d:%d", &c1, &c2, &c3, &h, &m);
            tiempo = diff(c1, c2, c3) + h * 60 + m;
            to[2 * i] = tiempo;

            scanf(" %c%c%c:%d:%d", &c1, &c2, &c3, &h, &m);
            tiempo = diff(c1, c2, c3) + h * 60 + m;
            from[2 * i + 1] = tiempo;
            scanf(" %c%c%c:%d:%d", &c1, &c2, &c3, &h, &m);
            tiempo = diff(c1, c2, c3) + h * 60 + m;
            to[2 * i + 1] = tiempo;
        }
        clear();
        for(int i = 0; i < 2 * n; i++) for(int j = i % 2 == 0 ? i + 2 : i + 1; j < 2 * n; j++)
        if(from[i] >= from[j] && from[i] < to[j] || from[j] >= from[i] && from[j] < to[i])
        addclause(i ^ 1, j ^ 1);    

        for(int i = 0; i < 2 * n; i++) if(times[i] == -1) scc(i);
        bool sirve = true;
        for(int i = 0; i < 2 * n; i += 2) if(comp[i] == comp[i + 1]) sirve = false;
        printf("%s\n", sirve ? "YES" : "NO");
    }
}

Este tipo de problemas no es muy común, pero viene. Ya vino en un mundial y en un sudamericano.

Destroying the Graph

Yo pensé que nadie resolvería este problema, felicitaciones a TheGoldenEagle por haber resuelto el problema.
Este es un problema de Min-Cut, así que creemos el grafo

s —- (W+)  —-  E —- (W-) —– t

s    : source
t    : target
W+    : V vértices, uno por cada vértice del grafo original
W-    : V vértices, uno por cada vértice del grafo original
E    : E vértices, uno por cada arista del grafo original

Para cada vértice del grafo original, hacemos una arista del W+ respectivo a los E respectivos, según las aristas que entran a dicho vértice en el grafo original. Para las aristas que salen, algo similar con W- y E. Los pesos de dichas aristas son infinitas.
Ahora, para cada vértice, hacemos una arista de s al W+ respectivo con peso igual al W+ del input. Lo mismo para W- y t.

Finalmente, lo que queremos es que cada arista esté cubierta, o bien por un W+, o bien por un W-, incluso por ambos. Esto equivale a romper aristas (s, W+) o (W-, t). Cada vez que rompemos una de esas aristas, estamos “eliminando” los W- o W+. Si existe un camino de s a t, entonces existe al menos una arista que tiene su W+ y su W- activos (recuerden que una arista tiene sólo un W- y sólo un W+).

Entonces, si desconectamos totalmente s de t, todos los E están cubiertos, y como queremos el menor costo para desconectar s de t, estamos hallando el min cut. Además, como las aristas incidentes a E son de peso infinito, nunca tocaremos esas aristas, sólo las de (s, W+) y las de (W-, t). Así, podemos resolver este problema con un algoritmo para max flow – min cut que pueda trabajar con al menos 5000 vértices.

Acá pongo mi code. Por cierto, hay implementaciones mucho más cortas.

#include<cstring>
#include<iostream>
#include<vector>
#include<algorithm>
#include<cstdio>
using namespace std;

const int maxnode=20000+5;
const int maxedge=1000000+5;
const int oo=1000000000;

int node,src,dest,nedge;
int head[maxnode],point[maxedge],next[maxedge],flow[maxedge],capa[maxedge];
int dist[maxnode],Q[maxnode],work[maxnode];

//inicializa el network, con _node igual a numero de nodos, _src como fuente y como _dest como sink
void init(int _node,int _src,int _dest)
{
     node=_node;
     src=_src;
     dest=_dest;
     for (int i=0;i<node;i++) head[i]=-1;
     nedge=0;
}

//añade la arista de u a v con capacidad c1 y la arista de v a u con capacidad c2
void addedge(int u,int v,int c1,int c2)
{
     point[nedge]=v,capa[nedge]=c1,flow[nedge]=0,next[nedge]=head[u],head[u]=(nedge++);
     point[nedge]=u,capa[nedge]=c2,flow[nedge]=0,next[nedge]=head[v],head[v]=(nedge++);
}

//bfs de dinic
bool dinic_bfs()
{
     memset(dist,-1,sizeof(dist));
     dist[src]=0;
     int sizeQ=0;
     Q[sizeQ++]=src;
     for (int cl=0;cl<sizeQ;cl++)
     for (int k=Q[cl],i=head[k];i>=0;i=next[i])
     if (flow[i]<capa[i] && dist[point[i]]<0)
     {
        dist[point[i]]=dist[k]+1;
        Q[sizeQ++]=point[i];
     }
     return dist[dest]>=0;
}

//dfs de dinic
int dinic_dfs(int x,int exp)
{
    if (x==dest) return exp;
    for (int &i=work[x];i>=0;i=next[i])
    {
        int v=point[i],tmp;
        if (flow[i]<capa[i] && dist[v]==dist[x]+1 && (tmp=dinic_dfs(v,min(exp,capa[i]-flow[i])))>0)
        {
           flow[i]+=tmp;
           flow[i^1]-=tmp;
           return tmp;
        }
    }
    return 0;
}
//flujo
int dinic_flow()
{
    int result=0;
    while (dinic_bfs())
    {
         for (int i=0;i<node;i++) work[i]=head[i];
         while (1)
         {
              int delta=dinic_dfs(src,oo);
              if (delta==0) break;
              result+=delta;
         }
    }
    return result;
}

int main(){
    int V, E; scanf("%d %d", &V, &E);
    int s = 2 * V + E;
    int t = s + 1;
    init(t + 1, s, t);

    for(int i = 0; i < V; i++){
        int mas; scanf("%d", &mas);
        addedge(s, i, mas, 0);
    }

    for(int i = 0; i < V; i++){
        int menos; scanf("%d", &menos);
        addedge(i + V, t, menos, 0);
    }

    for(int i = 0; i < E; i++){
        int u, v; scanf("%d %d", &u, &v); u--; v--;
        int ar = 2 * V + i;
        addedge(v, ar, oo, 0);
        addedge(ar, u + V, oo, 0);
    }

    cout << dinic_flow() << endl;
    vector<pair<int, int> > res;
    for(int i = 0; i < V; i++)
        if(dist[i] == -1)
           res.push_back(make_pair(i + 1, 1));

    for(int i = 0; i < V; i++)
        if (dist[i + V] != -1)
           res.push_back(make_pair(i + 1, 0));

    cout << res.size() << endl;
    sort(res.begin(), res.end());
    for(int i = 0; i < res.size(); i++){
        printf("%d %c\n", res[i].first, res[i].second == 1 ? '+' : '-');
    }
}

Cualquier duda o aporte respecto a los problemas pueden acceder al foro -> http://codebreakerscorp.foroperu.org/t37-aqp-training-6-2011 .

#include<cstring>
#include<vector>
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;

vector<int> E[101];
bool seen[101];

void dfs(int u){
   if(seen[u]) return;
   seen[u] = true;
   for(int i = 0; i < E[u].size(); i++)
       dfs(E[u][i]);
}

int main(){
   int t; cin >> t;
   while(t--){
       int n, m;
       cin >> n >> m;
       for(int i = 0; i < n; i++) E[i].resize(0);
       while(m--){
           int a, b; scanf("%d %d", &a, &b);
           a--; b--;
           E[a].push_back(b); E[b].push_back(a);
       }
       int res = 0;
       memset(seen, 0, sizeof seen);
       for(int i = 0; i < n; i++) if(!seen[i]) dfs(i), res++;
       cout << res << endl;
   }
}
Comments
  1. raynerhmc says:

    Xvr l editorial😀, pero pucha por otro lado.. lamento no haber participado, ni modo, asi es la vida..😦

  2. Digberth says:

    Excelente la editorial!!😀, la mejor!! felicidades…

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s