/****************************************************************************/
/* Teorema chino del resto                                                  */
/* Ref: Rosen "Elementary number theory..."                                 */
/*                                                                          */
/* Jaime Suarez <mcripto@bigfoot.com> 2003                                  */
/* en http://elparaiso.mat.uned.es                                            */
/****************************************************************************/

#include <stdio.h>

#include <stdlib.h>

#define PAR(x) !(x&1)

int tchino(int n, long *a, long *m, long *x);
int primos_a_pares(int n, long *m);
long mcd(long a, long b);
int invmod(long a, long m, long *inv);
void mcd_aux(long a, long b, long *alfa, long *beta, long *m);

int main()
{
	long *m,*a,x;
	int i,n;

	puts("Este programa resuelve sistemas de congruencias.");
	puts("del tipo x=a1 mod m1;... x=an mod mn.\n");
	printf("Número de ecuaciones n= "); scanf("%d",&n);
	m=(long *)malloc(n*sizeof(long));
	a=(long *)malloc(n*sizeof(long));
	if (a==NULL) {
		puts("No hay suficiente memoria.");
		return 1;
	}
	for (i=0; i<n; i++) {
		printf("\na%d  =",i+1); scanf("%ld",a+i);
		printf("m%d  =",i+1);   scanf("%ld",m+i);
	}
	if (tchino(n,a,m,&x)) {
	    puts("\nNo puedo solucionar ese sistema.");
	    return 1;
	}
	printf("\nLa solución es %ld.\n",x);
	return 0;
}

/*
 * Funcion tchino()
 * Descrip: resuelve un sistema de ecuaciones x=a1 mod m1... x=an mod mn
 *          siempre y cuando los modulos sean primos entre si dos a dos.
 * Entrada: n numero de ecuaciones,a coeficientes de las ecuaciones
 *          m modulos de las ecuaciones, x resultado del sistema
 * Salida : O --> Ok 1 --> Error, no se pudo solucionar el sistema
 * Efecto : si devuelve 0 la solucion del sistema esta en *x,
 *          en otro caso el valor de x no se modificara.
 */

int tchino(int n, long *a, long *m, long *x)
{
	long *M,*y,K=1;
	int i;

	M=(long *)malloc(n*sizeof(long));
	y=(long *)malloc(n*sizeof(long));
	if (y==NULL) return 1;
	if (primos_a_pares(n,m) != 1) return 1;
	for (i=0; i<n; i++) K*= *(m+i);
	for (i=0; i<n; i++) {
		*(M+i)= K / *(m+i);
		invmod(*(M+i),*(m+i),y+i);
	}
	*x= 0;
	for (i=0; i<n; i++)
		*x +=  *(a+i) * *(M+i) * *(y+i);
	*x %= K;
	return 0;
}

/*
 * funcion primos_a_pares()
 * Descrip: averigua si una serie de enteros son primos dos a dos
 * Entrada: n numero de enteros, m puntero a la lista de enteros
 * Salida : 0 no son primos entre si, 1 primos entre si de dos en dos.
 */

int primos_a_pares(int n, long *m)
{
	int i,j;

	for (i=0; i<n-1; i++)
		for (j=i+1; j<n; j++) {
			if (mcd(*(m+i),*(m+j))!=1) return 0;
		}
	return 1;
}

/*
 * funcion mcd()
 * Descrip: devuelve el mcd de dos enteros sin usar divisiones,
 *          solo restas y desplazamientos.
 */

long mcd(long a, long b)
{
	if (a==b) return a;
	else if (a<b) return mcd(b,a);
	else if ( PAR(a) &&  PAR(b))  return 2*mcd(a>>1,b>>1);
	else if ( PAR(a) && !PAR(b))  return mcd(a>>1,b);
	else if (!PAR(a) &&  PAR(b))  return mcd(a,b>>1);
	else                          return mcd(a-b,b);
}

/*
 * funcion invmod()
 * Descrip: devuelve el inverso de un entero modulo m si es que existe
 * Entrada: a un entero, m el modulo, inv contendra el resultado
 * Salida : 0-->Ok, 1-->Error, no inversible
 * Efecto : *inv contiene el inverso de a modulo m cuando sea posible
 */

int invmod(long a, long m, long *inv)
{
	long alfa, beta, r;

	mcd_aux(a,m,&alfa,&beta,&r);
	if (r!=1) return 1;
	*inv = alfa;
	if (*inv<0) *inv += m;
	return 0;
}

/* 
 * funcion mcd_aux()
 * Descrip: calcula el mcd de dos enteros y los coeficientes que
 *          permiten expresar dicho mcd como combinacion lineal
 *          de dichos enteros.
 * Entrada: a y b enteros long, alfa, beta y m punteros a long
 * Salida : ninguna
 * Efecto : *m contendra el mcd de a y b
 *          *alfa y *beta los enteros tales que m=alfa*a+beta*b
 */

void mcd_aux(long a, long b, long *alfa, long *beta, long *m)
{
	long s0,t0,s1,t1,s,t,q,r=1;
	
	if (a<b) return mcd_aux(b,a,beta,alfa,m);
	s0=1; t0=0;
	s1=0; t1=1;
	while (r != 0) {
		q = a/b;
		r = a%b;
		s = s0 - q*s1;
		t = t0 - q*t1;
		s0=s1; t0=t1;
		s1=s ; t1=t ;
		a =b ; b =r ;
	}
	*m=a;
	*alfa=s0;
	*beta=t0;
	return;
}