FINAL UTP: Soluciones de los problemas

Felicidades a todos los que participaron en el final de la Universidad Politecnica de Tlaxcala, aqui les dejo la solucion de los problemas

Problema A: Palabras
En este problema solo hay que ver si una cadena se puede formar con las letras de otra cadena

# include <stdio.h>
# include <string.h>

# define MAX 1000000

char wordA[MAX + 1], wordB[MAX+1];
int hash[256];

main(){
  int x;
  while( scanf("%s %s", wordA, wordB) != EOF ){
  	memset( hash, 0, sizeof( hash ));
  	for( x = 0; x < strlen(wordA); x++)
  	  hash[ wordA[x] ]++;
  	for( x = 0; x < strlen(wordB); x++)
  	  hash[ wordB[x] ]--;
  	 for( x = 0; x < 256 ; x++) if( hash[ x ] < 0 ) break;
  	 printf("%s\n", ( x < 256) ? "NO" : "YES");
  }
  return 0;	
}

Problema B: El señor de los anillos
Señor de los anillos, un problema que es definitivamente un dijkstra, lo complicado de este dijkstra es ver que nodos estan vistos por Sauron, como pueden ver las recurrencias son para los nodos que Sauron ve los nodos es de 1 a 7, de esa menera si sacamos el LCM de todos estos numeros tenemos esto (2*2*3*5*7) este es el modulo que necesitamos para saber si se repite sobre cualquier nodo, si nosotros ya calculamos el nodo X con el modulo Y, entonces ya no hay que evaluarlo de nuevo.

# include <stdio.h>
# include <string.h>

# define MOD (2*2*3*5*7)
# define MAX 102

int periodo[MAX+1];
int graph[MAX+1][MAX+1] , vis[MAX+1][MAX+1];

int distance[ MAX + 1 ][ MOD + 2];

main(){
  int n, m, dis, node, mod, x, y, a, b, nextnode;
  while( scanf("%d %d", &n, &m) != EOF && ( n || m )) {
     for( x = 1; x <= n; x++) scanf("%d", &periodo[ x ]);
     periodo[ 0 ] = MOD + 1;
     memset( graph, 0, sizeof( graph ));
     for( x = 0; x < m; x++){
       scanf("%d %d", &a, &b);
       graph[a][b] = graph[b][a] = 1;
     }
     for( x = 0; x <= n; x++) graph[x][x] = 1;
     for( node = 0; node <= n; node++)
     for( mod = 0; mod <= MOD; mod++){
       vis[ node ][ mod ] = 0;
       distance[ node ][ mod ] = (1<<30);	
     }
     node = 0;
     mod = 0;
     distance[ node ][ mod ] = 0;
     while( node != -1){
       dis = distance[ node ][ mod ];
       vis[node][ mod ] = 1;
       if( node == n ) break;
       for( nextnode = 0; nextnode <= n; nextnode++ ) if( graph[node][nextnode] == 1){
       	 if( distance[nextnode][ (mod + 1 ) % MOD ] > dis + 1 && (mod + 1) % periodo[ nextnode ] != 0 ){
       	   	distance[ nextnode ][ ( mod + 1 ) % MOD ] = dis + 1;
       	 }
       }
       node = -1;
       dis = (1<<30);
       for( x = 0; x <= n; x++)
         for( y = 0; y <= MOD; y++) if(vis[x][y] == 0 && distance[x][y] < dis){
            dis = distance[x][y];
            node = x;
            mod = y;
         }
     }
     printf("%d\n", ( node == -1 ) ? -1 : dis );
  }
  return 0;	
}

Problema C: Dados
Es un problema de conteo, ver de cuantas maneras distintas puedo colocar los dados para que me salga la suma solicitada, se puede hacer con un programacion dinamica o bien con un backtraking, que serviria de la misma manera, o bien cuatro fors con bastantes ifs.

# include <stdio.h>
# include <string.h>

# define MAX 5
# define MAX_CARAS 40
# define MAX_SUMA 100

int dados[MAX+1][MAX_CARAS+1];
int ndados[MAX+1];
int din[MAX+1][MAX_SUMA+1], S, n;

int solve(int position, int suma){
  int res = 0, x;
  if( suma > S ) return 0;
  if( position == n ) return S == suma ? 1 : 0;
  if( din[position][suma] != -1) return din[position][suma];
  for( x = 0; x < ndados[ position ]; x++)
    res += solve( position + 1, suma + dados[ position ][ x ]);
  return din[position][suma] = res;
}

main(){
  int  x, y, prod,  a , b;
  while( scanf("%d %d", &n, &S) != EOF && ( n || S)){
     prod = 1; 
     for( x = 0; x < n; x++){
       scanf("%d", &ndados[ x ]);	
       for(y = 0; y < ndados[ x ]; y++){
       	 scanf("%d", &dados[x][y]);
       }
       prod *= ndados[x];
     }	
     memset( din, -1, sizeof( din ));
     a = solve(0, 0);
     b = prod;
     if( a == b ) printf("1\n");
     else if( a == 0 ) printf("0\n");
     else printf("%d/%d\n", a , b);
  }
  return 0;
}

Problema D: Palíndromo
Se puede hacer con una programacion dinamica para que sea rapido o bien cuatro fors ya que el tamaño de la cadena es pequeña y saldria a tiempo.

# include <stdio.h>
# include <string.h>

# define MAX 100

int din[MAX+1][MAX+1];
char word[MAX+1];

int ispalindrome(int a, int b){
  if( a >= b) return 1;
  if(word[a] != word[b]) return 0;
  if( din[a][b] != -1) return din[a][b];
  return din[a][b] = ispalindrome( a + 1, b - 1);
}

main(){
  int a, b, n, cont;
  while( scanf("%s", word) != EOF ){
     memset( din, -1, sizeof( din ));
     n = strlen(word);
     cont = 0;
     for( a = 0; a < n; a++)
     for( b = a; b < n; b++)
       cont += ispalindrome(a, b );
     printf("%d\n", cont);
  }
  return 0;	
}

Problema E: Buscando Cuadrados
El problema de encontrar una submatrix sobre otras, en especifico sobre este problema como las matrices son pequeñas, entonces podemos hacer fuerza bruta, y con cuatro fors comparar si la matriz A esta en todas las posiciones iniciales que podamos colocarle en la matriz B, si queremos optimizarlo tendriamos que meternos un poco con el KMP, para poder hacer un conteo rapido.

# include <string.h>
# include <stdio.h>

# define MAX 100

char map[MAX+1][MAX+1], submap[MAX+1][MAX+1];

main(){
  int n, size, row, col, r, c, cont, query, q;
  while( scanf("%d", &n) != EOF && n ){
     for( row = 0; row < n; row++) scanf("%s", map[row]);
     scanf("%d", &query);
     for( q = 0; q < query ; q++){
        scanf("%d", &size);	
        cont = 0;
        for( row = 0; row < size ; row++) scanf("%s", submap[row]);
        for( row = 0 ; row < n; row++)
        for( col = 0 ; col < n; col++){
           for( r = 0; r < size && r + row < n ; r++){
             for( c = 0; c < size && c + col < n ; c++) 
               if( map[row+r][col+c] != submap[r][c]) break;
             if( c < size ) break;	
           }
           if( r >= size ) cont++;
        }
        printf("%d\n", cont);
     }
  }
  return 0;	
}

Problema F: Molde!
El problema nos indica que debemos hacer un convex hull, posteriomente sacar el perimetro y el area del convex encontrado.

# include <stdio.h>
# include <string.h>
# include <math.h>

# define MAX 100000

typedef struct { int x, y; } Point;

Point Poligono[MAX+1];
Point convex[MAX+1];
int vis[MAX+1];

int n;

double valumen, perimetro;

double abso( double a ){ return a < 0.0 ? -a : a; }

long long cross_product(Point a, Point b, Point c){
   return (b.x - a.x)*(c.y - a.y) - (c.x - a.x)*(b.y - a.y);
}

long long cross(Point A, Point B, Point C){
  Point AB;
  Point AC;
  AB.x = B.x-A.x;
  AB.y = B.y-A.y;
  AC.x = C.x-A.x;
  AC.y = C.y-A.y;
  return AB.x * AC.y - AB.y * AC.x;
}

double area2D_Polygon( int n, Point* V ) {
    double area = 0;
    int  i, j, k;   
    if (n < 3) return 0; 
    for (i=1, j=2, k=0; i<n; i++, j++, k++) {
        area += V[i].x * (V[j].y - V[k].y);
    }
    area += V[n].x * (V[1].y - V[n-1].y); 
    return abso(area / 2.0);
}

double distance( Point a, Point b ){
  return sqrt( (a.x - b.x)*(a.x - b.x) + (a.y - b.y)*(a.y - b.y) ) ;	
}


double solve( int n ){
   Point p, ptmp, pposible;
   int nconvex = 0, x, pos, posa, post;
   double area;
   memset( vis, 0, sizeof( vis ));
   if( n == 1) return 0;
   if( n == 2) return distance( Poligono[0], Poligono[1]) * 2.0;
   p = Poligono[0];
   pos = 0;
   for( x = 1; x < n; x++){
   	  if(p.x > Poligono[x].x || (p.x == Poligono[x].x && p.y > Poligono[x].y)){
   	     p = Poligono[x];
   	     pos = x;
   	  }
   }
   posa = pos;
   while( 1 ){
      vis[pos] = 1;	
      convex[nconvex++] = p;
      posa = pos;
      for(x = 0; x < n; x++){
      	if( vis[x] == 0){
      	   pos = x;
      	   break;	
      	}
      }
      for( x = x + 1; x < n; x++){
          if( cross( Poligono[posa],Poligono[x], Poligono[pos]) < 0 ){
         	pos = x;
         }else if(cross( Poligono[posa],Poligono[x], Poligono[pos])  == 0 && distance( Poligono[posa] , Poligono[ pos ])
         	       < distance( Poligono[posa] , Poligono[ x ])){
         	   pos = x;	
         }
      }
      p = Poligono[ pos ];
      if( vis[ pos ] == 1) break;
   }
   perimetro = 0.0;
   convex[ nconvex] = convex[ 0 ];
   for( x = 0; x < nconvex; x++) perimetro += distance( convex[ x ], convex[ (x + 1) % nconvex]);
   area =  area2D_Polygon( nconvex, convex);
   printf("%.2lf %.2lf %.2lf\n", perimetro, area, valumen / area);
}


main(){
  int x;
  while( scanf("%d %lf", &n, &valumen) != EOF && ( n != 0 || valumen != 0 )){
     for( x = 0; x < n; x++) scanf("%d %d", &Poligono[ x ].x, &Poligono[ x ].y);	
     solve( n );
  }
  return 0;	
}

Cualquier sugerencia, explicacion mas detallada, favor de enviarme un correo a rodrigo.burgos@gmail.com

Semifinal UPT Soluciones

Concurso Semifinal UTP

Felicidades a todos, por participar en este tipo de competicion, si quieren ver los problemas y resultados puede verlos en el blog oficial http://concursodeprogramacionupt.wordpress.com/ aqui les dejo los codigos de cada uno de los problemas espero les gusten.

Problema A:

El problema A se tenia que saber cual es el alumno con la coordenada Y mas cercana a 0, el unico truco seria ver que tambien puede ser negativo el valor.

# include <stdio.h>
# include <string.h>
# define MAX 100

typedef struct { char nombre[1000];  int X, Y; } CONTESTANT;

CONTESTANT contestant[ MAX + 1];

int abso( int a ){ return a < 0 ? -a : a; }

main(){
  int ncases , cases, ncontestant, closer, cntResponse, x;
  for( scanf("%d", &ncases), cases = 1 ; cases <= ncases ; cases++ ) {
  	scanf("%d", &ncontestant);
  	closer = (1<<30);
  	for( x = 0; x < ncontestant; x++){
  	   	scanf("%s %d %d", contestant[ x ].nombre, &contestant[ x ].X, &contestant[ x ].Y );
  	   	if( closer > abso( contestant[x].Y ) ){
  	   	  closer = abso( contestant[x].Y );
  	   	}
  	}
  	cntResponse = 0;
  	for( x = 0; x < ncontestant ; x++ ){
  	  if( closer == abso( contestant[x].Y)) {
  	    if( cntResponse != 0 ) printf(" ");
  	    printf("%s", contestant[x].nombre );
  	    cntResponse++;
  	  }	
  	}
  	printf("\n");
  }
  return 0;	
}

Problema B:

Un problema un poco dificil para aquellos que empiezan en esto de la programacion , pero necesario en cualquier concurso, son los problemas de programacion dinamica, este un poquito dificil ya que requeria programación dinamica mas mascara de bits

# include <stdio.h>
# include <string.h>

int n;
char map[6][1002];
int din[1<<6][1002];
int block[2][6];

int min( int a, int b ){ return a < b ? a : b; }

int contbits( int mask ){ return mask == 0 ? 0 : contbits( mask >> 1 ) + ( mask & 1); }

int solve(int r, int c, int dr, int dc, int position){
    int res = (1<<22), cst;
	if( r >= 5 || r < 0 || c >= 2 ) return (1<<22);
	if( r == dr && c == dc ) return map[r][position] - '0';
   	if( block[c][r] == 1 || block[c][r] == 2 || block[c][r] == 10) return (1<<22);
   	block[c][r] = 10;
   	cst = (map[r][position] == '@') ? 0 : map[r][position]-'0';
   	res = min( res, solve( r+1, c, dr , dc, position )+ cst);
   	res = min( res, solve( r-1, c, dr , dc, position )+ cst);
   	res = min( res, solve( r, c+1, dr , dc, position+1 )+ cst);
   	block[c][r] = 0;
   	return res;
}

int mark( int r, int c, int dr, int dc, int position, int rt, int aspectedResult){
	int cst;
	if( r >= 5 || r < 0 || c >= 2 || rt > aspectedResult) return 0;
	if( r == dr && c == dc ){
	   if( aspectedResult == rt + map[r][position] - '0'){
 	     block[c][r] = 1;
	     return 1;	
	   }
	   return 0;
	}
	if( block[c][r] == 1 || block[c][r] == 2 || block[c][r] == 10) return 0;
	block[c][r] = 10;
	cst = map[r][position] == '@' ? 0 : map[r][position]-'0';
	if( mark( r+1, c, dr , dc, position, cst + rt, aspectedResult ) ){
	  block[c][r] = 1;
	  return 1;	
	}
   	 if( mark( r-1, c, dr , dc, position, cst + rt, aspectedResult  )){
	  block[c][r] = 1;
	  return 1;	
	}
   	 if( mark( r, c+1, dr , dc, position+1, cst + rt, aspectedResult )){
	  block[c][r] = 1;
	  return 1;	
	}
	block[c][r] = 0;
	return 0;
}

int cost( int position, int mask, int newmask ){
  int x, last, y, a, b;
  int r = 0, rt;
   memset( block, 0, sizeof( block ));
   if( position != 0 ) for( x = 0; x < 5; x++) block[0][x] = 1;
   for( x = 0 ; x < 5; x++) if( ( mask & (1<<x) )   > 0 ) block[0][x] = 2;
   for( x = 0 ; x < 5; x++) if( ( newmask & (1<<x) )   > 0 ) block[1][x] = 2;
   last = 0;
   for( x = 0 ; x < 5; x++) 
     if( ( mask & (1<<x) )   > 0 ) {
        for( y = last ; y < 5; y++) if( ( newmask & (1<<y) )   > 0 ){
        	block[0][x] = 0;
        	block[1][y] = 0;
        	rt = solve(x, 0, y, 1, position);
        	if( rt < (1<<22)){
        	  mark( x, 0, y, 1, position, 0, rt);
        	  r += (rt - ( map[x][position] == '@' ? 0: map[x][position] -'0'));
        	} else r = rt;
        	last = y+1;
        	block[0][x] = 2;
        	block[1][y] = 2;
        	break;
        }
     }
   return r;
}

int alfalfasMoves( int mask, int position ){
  int newmask, cst, tmp;
  int res = (1<<22);
  if( position >= n -1 ) return 0;
  if(din[mask][position] !=-1) return din[mask][position];
  for(newmask = 0; newmask < (1<<5) ; newmask++) if( contbits( newmask) == 3){
  	cst =  cost( position, mask, newmask ) ;
  	if( cst < (1<<22)){
  		cst += alfalfasMoves( newmask, position + 1);
  		if( cst < res ){
  			tmp = newmask;
  	  		res = min( res, cst ) ;
  		}
  	}
  }  
  return din[mask][position] = res;
}

main(){
  int r, mask, x;
  while( scanf("%d", &n ) != EOF && n ) {
    for( x = 0; x < 5; x++) scanf("%s", map[ x ]);
    mask = 0;
    for( x = 0 ; x < 5; x++) 
      if( map[x][0] == '@' ){
        mask |= (1<<x);	
      }
    memset( din, -1, sizeof( din ));
    r =  alfalfasMoves(mask, 0);
    printf("%d Jijuan%s\n", r , (r == 1 ? "" : "s")); 
  }
  return 0;	
}

Problema C:

Es un problema de busqueda en profundidad, o amplitud, aun que es mas secillo programar la profunidad marcando en la matriz VIS los lugares a dondes puede ir Diego.

# include <stdio.h>
# include <string.h>

# define MAX 100

char map[MAX+1][MAX+1];
int vis[MAX+1][MAX+1];
int R, C;
int dx[4] = {1,-1,0,0};
int dy[4] = {0,0,1,-1};

void mark( int r, int c ){
  int dir;
  if( r < 0 || r >= R || c < 0 || c >= C ) return;	
  if( vis[r][c] == 1 || map[r][c] == 'M') return;
  vis[r][c] = 1;
  for( dir = 0; dir < 4; dir++)
    mark( r + dx[ dir ], c + dy[ dir ]);
}

main(){
  int ncases, cases, row;
  int ri, ci, rd, cd;
  for( scanf("%d", &ncases), cases = 1; cases <= ncases ; cases++ ){
    scanf("%d %d", &R, &C);
    for( row = 0; row < R; row++){
      scanf("%s", map[ row ]);	
    }	
    scanf("%d %d", &ri, &ci);
    scanf("%d %d", &rd, &cd);
    memset( vis, 0, sizeof( vis ) );
    mark( ri - 1 , ci - 1 );
    if( vis[ rd - 1][ cd - 1] == 1 ) printf("SI\n");
    else printf("NO\n");
  }
  return 0;	
}

Problema D:
Problema de contar caracteres, practicamente es un for que recorra la cadena de la posicion A a la posicion B contando el caracter.

# include <string.h>
# include <stdio.h>

# define MAX_TEXT 100000

char text[MAX_TEXT + 1];

main(){
  int ncases, cases;
  int a, b, q, nquery, cont;
  char car;
  for( scanf("%d", &ncases), cases = 1; cases <= ncases ; cases++ ){
    scanf("%s", text);
    scanf("%d", &nquery);	
    for( q = 0; q < nquery; q++){
    	scanf(" %c %d %d", &car, &a, &b);
    	a--; b--;
    	for( cont = 0 ; a <= b ; a++ ) 
    	  if( text[ a ] == car ) cont++;
    	printf("%d\n", cont);
    }
  }
  return 0;	
}

Problema E:

El problema mas sencillo del concurso, solo hay que imprimir la tabla de multiplicar del numero dado, aqui tal vez lo que les falto en el concurso y por el motivo que fueran muchos Time Limit time limit exceeded, es por que tenian que espera esta el fin de archivo, en entrada estadar como en cualquier file, podemos leer el scanf hasta un EOF que es fin de archivo, vean el codigo para mayor comprension, tambine pueden usar la funcion foef( FIEL *pf), pero en vez de un archivo solo colocando feof( stdin ) , que indicaria entrada estandar.

# include <string.h>
# include <stdio.h>

main(){
  int dig, n;
  while( scanf("%d", &n) != EOF ){
  	for( dig = 1; dig <= 10; dig++)
  	  printf("%dx%d=%d\n", n, dig, dig * n );
  }
  return 0;	
}

Problema F:

El problema F es un problema de geometria computacional, basicamente con solo saber el algoritmo de punto en un poligono podemos resolver este problema.


# include <stdio.h>
# include <string.h>

# define MAX_POLIGON 100
# define MAX_ELEMENT 200

typedef struct { int x, y; } Point;

Point Poligon[MAX_POLIGON+1][MAX_ELEMENT+2];
int npoligon[MAX_POLIGON + 1];


int cn_PnPoly( Point P, Point* V, int n ){
    int i;
    int    cn = 0; 
    for ( i=0; i<n; i++) {    
       if (((V[i].y <= P.y) && (V[i+1].y > P.y))     /* an upward crossing */
        || ((V[i].y > P.y) && (V[i+1].y <=  P.y))) { /* a downward crossing */
            
            double vt = (double)(P.y  - V[i].y) / (double)(V[i+1].y - V[i].y);
            if (P.x <  V[i].x + vt * (V[i+1].x - V[i].x)) 
                 ++cn; 
        }
    }
    return (cn&1); 

}

int hashInsidePoligon[MAX_POLIGON+1];

main(){
  int n, cont, x, y, m;
  Point source;
  while( scanf("%d", &n) != EOF && n ){
    for( x = 0; x < n; x++){
      scanf("%d", &npoligon[ x ]);	
      for( y = 0; y < npoligon[ x ] ; y++) scanf("%d %d", &Poligon[x][y].x, &Poligon[x][y].y);
      Poligon[x][y]  = Poligon[x][y-1];
    }
    memset(hashInsidePoligon, 0, sizeof(hashInsidePoligon));
    scanf("%d", &m);
    for( x = 0; x < m ; x++){
       scanf("%d %d", &source.x, &source.y );	
       for( y = 0; y < n; y++) 
         if( cn_PnPoly( source, Poligon[y] , npoligon[ y ]) )  hashInsidePoligon[ y ] = 1;
    }
    cont = 0;
    for( x = 0; x < n; x++) if(hashInsidePoligon[x] == 1) cont++;
    printf("%d\n", cont);
  }
  return 0;	
}

Cualquier duda o si quieren que explique mas a grandes rasgos una solucion me puede escribir a rodrigo.burgos@gmail.com

Adding New Machine: Regional de Dalian

por: Rodrigo Burgos Domínguez.

 

Problema B: Adding New Machine

Descripción

Increíble loca Compañía progresista ( ICPC por sus siglas en ingles), sufre demasiado por su lenta producción. Después de una investigación, ellos encontraron que el embotellamiento estaba en el Absulutely Crowded Manufactory (ACM). Para acelerar el procedimiento, ellos compraron otra maquina para el ACM. Pero un nuevo problema apareció, ¿como poner esta nueva maquina en el ACM?.

ACM es un complejo rectangular, que esta dividido en W*H celdas. Existen N viejas maquinas rectangulares,  y la nueva maquina no puede ocupar ninguna celda donde existan maquinas viejas. La nueva maquina necesita M celdas consecutivas. Celdas consecutivas significan una línea de celdas adyacentes. Tu tarea es calcular el numero de formas diferentes en las que se pueden colocar la nueva maquina.

Entrada

Hay múltiples casos de prueba ( no mas de 50). La primera línea de cada caso de prueba contiene 4 enteros W, H, N , M ( 1 <= W, H  <= 10^7 , 0 <= N <= 50000, 1 <= M <= 1000), indicando el ancho y el largo de el ACM , el numero de maquinas viejas  y el tamaño de la nueva maquina. Las siguientes N líneas, cada una contiene 4 enteros Xi1, Yi1 , Xi2 y Yi2 ( 1 <= Xi1 <= Xi2 <= W, 1 <= Yi1 <= Yi2 <= H), indicando las coordenadas de la i-e sima maquina vieja. Se garantiza que no hay celdas ocupadas por dos maquinas viejas.

Salida

Imprimir el numero de formas de colocar la nueva maquina en el ACM.

Entrada Ejemplo Salida Ejemplo
3 3 1 22 2 2 23 3 1 32 2 2 22 3 2 2

1 1 1 1

2 3 2 3

843

Solución:

La primera solución que se nos presenta a simple vista es colocar en todas las posiciones vacías la nueva maquina, y contar cuantas veces se puede colocar esta maquina, el único problema es que W y H son muy grandes, y el costo de hacer esto es W*H*M ( 10^17 ), por lo tanto no es una solución viable,  pero de esta solución podemos sacar algunos conclusiones que nos pueden ser útiles, por ejemplo cuando exploramos todas las posibilidades solo existen dos maneras de colocar una maquina iniciando en una posiciones (fila, columna), una es colocándola horizontalmente y la otra es verticalmente.

Para resolver un problema complicado primero hay que irnos a lo particular, y de ahí movernos a lo general y es por eso que vamos a resolverlo parte por parte de este problema.

Primero supongamos que solo es una línea sin obstáculos, si la línea es de tamaño H y la longitud de la maquina es M, el numero de formas que podemos colocar M es H – M + 1 , siempre y cuando M sea menor a H.

Si todas las celdas de la matriz estuvieran libres de obstáculos entonces podríamos contar  con facilidad de cuantas maneras podemos colocar esta nueva maquina en toda la matriz.  Suponiendo que la colocamos de manera vertical, podemos calcular cada columna en 10^7 operaciones ( bueno pero como todos es lo mismo podría ser con tan solo una multiplicación para todas las columnas), para en este caso nos conviene realizar columna por columna, en el caso de las filas cuando la maquina se coloca horizontalmente también se puede contar fila por fila, en a lo mas 10^7 operaciones.

Esto es para el caso en que toda están vacías, con una sola resta funciona, pero ¿que pasa cuando existen maquina ya colocadas y cuando son 50000 de ellas?. Para ese caso debemos primero observar un ejemplo, vean la imagen siguiente:

Figura 1.

Como pueden ver, ningún rectángulo se empalma, si nosotros vamos de izquierda a derecha, por cada columna vemos que en el a primera columna no hay nada que nos limite, nosotros calculamos cuantas maquinas nuevas podemos colocar en esta columna con una simple resta, pero en la segunda existe dos rectángulos, cuando inician los rectángulos nosotros debemos calcular exactamente los intervalos vacíos para calcular el numero de maquinas a colocar, existen dos intervalos de 1 y 2, como nuestras maquina es de tamaño 2 , entonces podemos colocar  0 y 1 maquinas nuevas, si nosotros nos movemos a la tercera columna el numero de maquina que vamos a colocar es la misma que en la segunda, ya que no hay ningún rectángulo que termine o inicie, pero en la cuarta columna nos encontramos que uno de los rectángulos se termina, y hay que calcular de nuevo la cantidad de maquinas a colocar. En este caso quedaría un intervalo de 5, la cantidad de maquinas que debemos colocar en esta columna es de 4.

¿Que es lo que podemos rescatar de esta parte?

Nosotros podemos ver que solamente hay que calcular las posiciones donde exista un cambio, donde se agregue un nuevo rectángulo o donde se termine uno, en total el máximo numero de posiciones donde pudiera ocurrir es esto son 100,000 ( ya que hay 50,000 rectángulos  por dos ya que es donde inicia un rectángulo y donde termina).

¿Pero como podemos calcular cada columna el numero de nuevas maquina que debemos colocar? La respuesta es utilizar una estructura de datos para contarlas.

¿Qué estructura de datos utilizaríamos para contar los segmentos unidos y restara M a cada uno de ellos?

Un Árbol de Intervalos nos serviría para nuestros propósitos,(Véase el capítulo de estructura de datos avanzadas), el Árbol de Intervalos nos puede ayudar al conteo de estos segmentos, ya que para cada nodo podemos guardar:

1 – numero de maquinas que podemos contar sin contar la de los extremos.

2 – numero de posiciones no marcadas pegadas a la izquierda .

3 – numero de posiciones no marcadas a la derecha.

Supongamos que tenemos una maquina de tamaño 1×2, este es el tamaño de la maquina que queremos insertar, la siguiente figura muestra una fila vacía, sin ningún rectángulo en ella.

Fig 2.

Si nosotros queremos insertar una maquina que va de la columna 2 a la columna 6 y otra maquina de la 8 a la 10 quedaría de  esta manera.

Fig 3.

En nuestro árbol de intervalos, quedaría de la siguiente manera:

Fig 4.

Cado nodo padre puede sacar la información de sus hijos, por ejemplo en el nodo que tiene la información del segmento [1, 8] , puede calcularlo con sus dos hijos, el [1,4] y el [5,8], para calcular la información se sigue los siguientes pasos.

  • Cuadros izquierdos : Son los cuadros izquierdos del hijo izquierdo.
  • Cuadros derechos: Son los cuadros derechos del hijo derecho.
  • Numero de maquinas:  La suma del numero de maquinas de los dos hijos mas la suma de los  cuadros derechos del hijo izquierdo y los cuadros izquierdo del hijo derecho menos M ( M es el tamaño de la nueva maquina), si el resultado es negativo entonces colocar 0.

¿Que pasa cuando uno de los dos hijos nodos esta completamente lleno?

En ese caso el calculo es distinto, el hijo que tiene todos los cuadros llenos, no tiene un lado izquierdo o derecho con cuadros , si no todo el segmento esta completo y tanto el lado izquierdo como derecho son los mismos cuadros.

Para el caso especifico del nodo que representa el segmento [1,4] del ejemplo anterior, que se muestra en la siguiente figura:

Se calcula de la siguiente manera

  • Cuadros izquierdos:  cuadros izquierdos del hijo izquierdo.
  • Cuadros derechos: la suma de los cuadros del hijo derecho mas los cuados derecho del hijo izquierdo.
  • Numero de maquinas: El numero de maquinas del lado izquierdo.

Como pueden ver se tiene que validar si esta completo alguno de sus hijos o si los dos están llenos, en el caso de que alguno este vacío aplica los mismo que en el primer escenario.

¿Pero por que es tan rápido un árbol de intervalos en este problema?

Cuando agregamos un nuevo cuadrado al árbol, iniciamos de la raíz el nodo que representa a todo el intervalo [1,N],  y nos movemos por sus hijos, ya en los hijos nos vamos recursivamente por todos las ramas del árbol hasta el final. Con las siguiente condiciones:

  1. Si el nodo donde estamos su intervalo ya no esta dentro del que queremos hacer una modificación , no seguimos exploras.
  2. Si el nodo donde estamos su intervalo queda absorbido por el intervalo donde vamos a modificar entonces solo modificamos ese nodo, y dejamos una bandera ahí, diciendo que todos los cuadros de este segmento o están usados o están libres, y ya no seguimos con los demás.
  3. Si no pasa ninguna de las anteriores, entramos a los hijos del nodo actual y realizamos el paso 1 y 2.

Y como pueden ver no se explora todo el árbol en cada modificación si no solo una parte de el, que seria dos veces el largo de las ramas y dependiendo del la longitud del segmento seria la longitud de la rama igual a  log( longitud del segmento ).

Para saber después de nuestras inserciones y eliminaciones cuantas maquinas caben en la fila o columna procesando, seria de la siguiente manera:

El numero de maquinas es igual a el numero de maquinas de la raíz mas max( 0,  los cuadros del lado izquierdo – M) + max( 0, los cuadros del lado derecho – M ) , donde M es el tamaño de la maquina.

Nuestra bandera seria un nuevo elemento del árbol.

 

Nota: Uno debe notar que cuando M es 1 solo hay que explorándolo vertical o horizontalmente, ya que su rotación vertical u horizontal es la misma, y solo contaríamos el doble.

Código

/*
 Autor: Rodrigo Burgos Dominguez
*/
# include <stdio.h>
# include <string.h>

#define MAXRECTANGLES 50002

int W, H, N, M, nbreakp, memtree;

typedef struct { int X[2], Y[2];} RECTANGLE;
typedef struct { int izq, der, countL, countR, count, delete, fill; } INTERVALTREE;
typedef struct { int coorden, position, accion; } BREAKPOINT;

INTERVALTREE MEM[ MAXRECTANGLES * 30 ];
RECTANGLE R[MAXRECTANGLES + 1];
BREAKPOINT BP[ (MAXRECTANGLES + 1) * 2];

int comparebp( BREAKPOINT *A, BREAKPOINT *B ){
  if(A->coorden != B->coorden ) return A->coorden - B->coorden;
  return B->accion - A->accion;
}

int min(int a, int b ){ return a < b ? a : b; } int max(int a, int b ){ return a > b ? a : b; }

INTERVALTREE getNode(int punter, int A, int B){
  INTERVALTREE tmp;
  tmp.countL = 0;
  tmp.countR = 0;
  tmp.count = 0;
  tmp.fill = 0;
  tmp.delete = 0;
  if( punter != -1 && MEM[punter].delete == 0 ) return MEM[ punter ];
  if( punter == -1 || MEM[punter].delete == 2 ){
    tmp.countL = B - A + 1;
    tmp.fill = 1;
  }
  return tmp;
}

void insert( int *punter, int A, int B, int a, int b, int type){
  int med = (A + B ) / 2;
  INTERVALTREE left, rigth;
  if( B < a || b < A ) return;   if(*punter == -1 ){      *punter = memtree++;      MEM[*punter].izq = -1;      MEM[*punter].der = -1;      MEM[*punter].count = 0;      MEM[*punter].countR = 0;      MEM[*punter].fill = 1;      MEM[*punter].countL = ( B - A + 1);      MEM[*punter ].delete = 0;   }else      if( MEM[*punter ].delete >= 1){
       if( MEM[*punter].izq != -1) MEM[MEM[*punter].izq].delete = MEM[*punter ].delete;
       if( MEM[*punter].der != -1) MEM[MEM[*punter].der].delete = MEM[*punter ].delete;
       MEM[*punter ].delete = 0;
     }
  if( a <= A && B <= b ){
     MEM[*punter].countL = (  type == 0 ? 0 : B - A + 1);
     MEM[*punter].fill = type;
     MEM[*punter].countR = 0;
     MEM[*punter].count = 0;
     if( MEM[*punter].izq != -1) MEM[MEM[*punter].izq].delete = 1 + type;
	 if( MEM[*punter].der != -1) MEM[MEM[*punter].der].delete = 1 + type;
     return;
  }
  insert(&MEM[*punter].izq, A, med, a, b, type);
  insert(&MEM[*punter].der, med+1, B, a, b, type);
  /* Calcular */
  left = getNode(MEM[*punter].izq , A, med);
  rigth = getNode(MEM[*punter].der, med +1 , B );
  if( left.fill == 1 && rigth.fill == 1 ){
    MEM[*punter].fill = 1;
    MEM[*punter].countL = B - A + 1;
	MEM[*punter].countR = 0;
	MEM[*punter].count = 0;
  }else if( left.fill == 1 ){
       MEM[ *punter].fill = 0;
       MEM[*punter].countL = left.countL + rigth.countL;
       MEM[*punter].countR = rigth.countR;
       MEM[*punter].count= rigth.count;
  }else if( rigth.fill == 1 ){
       MEM[ *punter].fill = 0;
       MEM[*punter].countR = left.countR + rigth.countL;
       MEM[*punter].countL = left.countL;
       MEM[*punter].count= left.count;
  }else{
         MEM[*punter].fill = 0;
	     MEM[*punter].countL = left.countL;
	     MEM[*punter].countR = rigth.countR;
	     MEM[*punter].count = left.count+ rigth.count+
	                           max( 0, left.countR + rigth.countL - M + 1) ;
  }
}

int count( int punter, int A, int B){
  if( punter == -1 ) return ( B - A + 1 ) - M + 1;
  if( MEM[punter].delete == 2 ) return ( B - A + 1 ) - M + 1;
  if( MEM[punter].delete == 1 ) return 0;
  return MEM[punter].count + max( 0 , MEM[punter].countL - M + 1) + max( 0, MEM[punter].countR - M + 1);
}

long long process( int axis , int width, int length){
   int lastpos = 1, cpos, indice = -1, A, B, pos;
   long long currentCont, response = 0;
   memtree = 0;
   for( pos = 0; pos < nbreakp; pos++){
   	 if( lastpos != BP[pos].coorden ){
       currentCont = (long long)count( indice, 1, length);
       response += currentCont * (long long)( BP[ pos ].coorden - lastpos);
   	 }
   	 cpos = BP[ pos ].coorden;
   	 A = ( axis == 0 ) ? R[ BP[ pos ].position ].Y[ 0 ] : R[ BP[ pos ].position ].X[ 0 ];
   	 B = ( axis == 0 ) ? R[ BP[ pos ].position ].Y[ 1 ] : R[ BP[ pos ].position ].X[ 1 ];
   	 insert( &indice , 1, length, A, B, BP[ pos ].accion );
   	 lastpos = cpos;
   }
   currentCont = (long long)count( indice, 1, length);
   response += currentCont * (long long)( width - lastpos + 1);

   return response;
}

main(){
  int rec, nc;
  long long cont;
  while( scanf("%d %d %d %d", &W, &H, &N, &M) != EOF ){
    for( rec = 0; rec < N; rec++)
       scanf("%d %d %d %d", &R[ rec ].X[0], &R[ rec ].Y[0], &R[ rec ].X[1], &R[ rec ].Y[1]);
    for( nbreakp = rec = 0; rec < N; rec++){
       for(nc = 0; nc < 2; nc++){
         BP[ nbreakp  ].position = rec;
         BP[ nbreakp  ].accion = nc;
         BP[ nbreakp++].coorden = R[rec].X[ nc ] + nc;
       }
    }
    qsort(BP, nbreakp, sizeof( BREAKPOINT ), comparebp );
    cont = process( 0, W , H);
    for( nbreakp = rec = 0; rec < N; rec++){
       for(nc = 0; nc < 2; nc++){          BP[ nbreakp  ].position = rec;          BP[ nbreakp  ].accion = nc;          BP[ nbreakp++].coorden = R[rec].Y[ nc ] + nc;         }     }     qsort(BP, nbreakp, sizeof( BREAKPOINT ), comparebp );     if( M > 1 )
    cont += process( 1, H, W);
    printf("%lld\n", cont);
  }
  return 0;
}

A Game with Candies : Final ACM-ICPC 2012 de Ciudades Mexicanas

Por: Rodrigo Burgos Dominguez

Apenas paso la final de ciudades Mexicanas, revisando los problema me encontré con este problema en particular A Game with Candies , lo primero que se me ocurrió fue la teoría de los Grundy Numbers, pero no salía con XOR, después de un rato de pensar me di cuenta que para que el primer jugador ganara tendría siempre que dejar al siguiente jugador con un numero par de números mayores, asi cuando el tirará siempre fuera impar y tuviera la forma de llavarlo a par, con esta idea surgen los siguientes casos:

Primero si el numero de elementos mayores es par o bien todos son ceros, el primero jugador pierde ya que siempre va a volver impar el numero de elementos mayores. En otro caso Gana.

// en el arreglo mx el elemento 0 es el mayor de los números dados
// el mx[1] es el segundo mayor, y el arreglo count[ valor ] contiene
// el numero de veces que se ingreso el numero ‘valor’</pre>
if( count[ mx[0] ] % 2 == 0 || mx[0] == 0){
    printf("0\n");
    continue;
}

Si el numero de elementos con valor mayor es mas de uno , entonces la respuesta el numero de elemento mayores count[mx[0]] por el elemento mayor ( ya que podemos poner 0, 1, …, mx[0] -1 ) y volvemos par el numero de elementos mayores.

 if( count[mx[0]] > 1 ){
   printf("%lld\n", (long long)(count[mx[0]]) * (long long)( mx[0]));
 }

Que pasa en el caso que mx[0] es igual a 0 ( si el elemento mas grande que se dio es igual a 0 ), esto indica que ya todos están en 0, por lo tanto el primero jugador piede.

if( mx[1] == 0){
  printf("1\n");
}

En otro caso, máximo valor es único y no es cero, por tanto al que ver los dos casos cuando el segundo valor mas grande es par o impar:
Cuando es par , podemos volver al elemento mayor 0 , 1, … mx[1] – 1 , y cuando es impar solamente podemos colocarlo de una manera , igual al segundo mas grande, para forzarlo a ser par.

else if( count[ mx[1] ] % 2 == 0 ){
   printf("%d\n", mx[ 1 ] );
}else{
  printf("%d\n", 1);
}

Les dejo el código:

/*

Autor: Rodrigo Burgos Dominguez.

*/
# include <stdio.h>
# include <string.h>

int count[1000001];

main(){
  int ncases, cases, mx[3], x, y, n, value;
  for( scanf("%d", &ncases), cases = 1; cases <= ncases ; cases++ ) {
   scanf("%d", &n);
   memset( count, 0, sizeof( count ));
   memset( mx, 0, sizeof( mx ));
   for( x = 0; x < n; x++){
     scanf("%d", &value);
     count[ value ]++;
     for(y = 0; y < 2; y++) if( value > mx[ y ]){
       mx[ y + 1 ] = mx[ y ];
       mx[ y ] = value;
       break;
     } else if( value == mx[ y ]) break;
   }
   if( count[ mx[0] ] % 2 == 0 || mx[0] == 0){
     printf("0\n");
     continue;
   }
   if( count[mx[0]] > 1 ){
     printf("%lld\n", (long long)(count[mx[0]]) * (long long)( mx[0]));
   }else{
     if( mx[1] == 0){
      printf("1\n");
     } else if( count[ mx[1] ] % 2 == 0 ){
      printf("%d\n", mx[ 1 ] );
     }else{
       printf("%d\n", 1);
     }
   }
 }
return 0;
}