/*The magic pairs problem:

Let SumFact(n) be the sum of factors
of n.

Find all n1,n2 in a range such that

SumFact(n1)-n1-1 == n2	and
SumFact(n2)-n2-1 == n1

-----------------------------------------------------
To find SumFact(k), start with prime factorization:

k=(p1^n1)(p2^n2) ... (pN^nN)

THEN,

SumFact(k)=(1+p1+p1^2...p1^n1)*(1+p2+p2^2...p2^n2)*
(1+pN+pN^2...pN^nN)

PROOF:

Do a couple examples -- it's obvious:

48=2^4*3

SumFact(48)=(1+2+4+8+16)*(1+3)=1+2+4+8+16+3+6+12+24+48

75=3*5^2

SumFact(75)=(1+3)*(1+5+25)		=1+5+25+3+15+75

Corollary:

SumFact(k)=SumFact(p1^n1)*SumFact(p2^n2)*...*SumFact(pN^nN)

*/

//Primes are needed to sqrt(N).  Therefore, we can use U32.
class PowPrime
{
	I64 n;
	I64 sumfact; //Sumfacts for powers of primes are needed beyond sqrt(N)
};

class Prime
{
	U32			 prime, pow_count;
	PowPrime	*pp;
};

I64 *PrimesNew(I64 N, I64 *_sqrt_primes, I64 *_cbrt_primes)
{
	I64		 i, j, sqrt = Ceil(Sqrt(N)), cbrt = Ceil(N ` (1 / 3.0)),
			 sqrt_sqrt = Ceil(Sqrt(sqrt)), sqrt_primes = 0, cbrt_primes = 0;
	U8		*s = CAlloc((sqrt + 1 + 7) / 8);
	Prime	*primes, *p;

	for (i = 2; i <= sqrt_sqrt; i++)
	{
		if (!Bt(s, i))
		{
			j = i * 2;
			while (j <= sqrt)
			{
				Bts(s, j);
				j += i;
			}
		}
	}
	for (i = 2; i <= sqrt; i++)
		if (!Bt(s, i))
		{
			sqrt_primes++; //Count primes
			if (i <= cbrt)
				cbrt_primes++;
		}

	p = primes = CAlloc(sqrt_primes * sizeof(Prime));
	for (i = 2; i <= sqrt; i++)
		if (!Bt(s, i))
		{
			p->prime = i;
			p++;
		}
	Free(s);

	*_sqrt_primes = sqrt_primes;
	*_cbrt_primes = cbrt_primes;

	return primes;
}

PowPrime *PowPrimesNew(I64 N, I64 sqrt_primes, Prime *primes, I64 *_num_powprimes)
{
	I64			 i, j, k, sf, num_powprimes = 0;
	Prime		*p;
	PowPrime	*powprimes, *pp;

	p = primes;
	for (i = 0; i < sqrt_primes; i++)
	{
		num_powprimes += Floor(Ln(N) / Ln(p->prime));
		p++;
	}

	p = primes;
	pp = powprimes = MAlloc(num_powprimes * sizeof(PowPrime));
	for (i = 0; i < sqrt_primes; i++)
	{
		p->pp = pp;
		j = p->prime;
		k = 1;
		sf = 1;
		while (j < N)
		{
			sf += j;
			pp->n = j;
			pp->sumfact = sf;
			j *= p->prime;
			pp++;
			p->pow_count++;
		}
		p++;
	}
	*_num_powprimes = num_powprimes;

	return powprimes;
}

I64 SumFact(I64 n, I64 sqrt_primes, Prime *p)
{
	I64			 i, k, sf = 1;
	PowPrime	*pp;

	if (n < 2)
		return 1;
	for (i = 0; i < sqrt_primes; i++)
	{
		k = 0;
		while (!(n % p->prime))
		{
			n /= p->prime;
			k++;
		}
		if (k)
		{
			pp = p->pp + (k - 1);
			sf *= pp->sumfact;
			if (n == 1)
				return sf;
		}
		p++;
	}

	return sf * (1 + n); //Prime
}

Bool TestSumFact(I64 n, I64 target_sf, I64 sqrt_primes, I64 cbrt_primes, Prime *p)
{
	I64			 i = 0, k, b, x1, x2;
	PowPrime	*pp;
	F64			disc;

	if (n < 2)
		return FALSE;
	while (i++ < cbrt_primes)
	{
		k = 0;
		while (!(n % p->prime))
		{
			n /= p->prime;
			k++;
		}
		if (k)
		{
			pp = p->pp + (k - 1);
			if (ModU64(&target_sf, pp->sumfact))
				return FALSE;
			if (n == 1)
			{
				if (target_sf == 1)
					return TRUE;
				else
					return FALSE;
			}
		}
		p++;
	}
/*	At this point we have three possible cases to test
1)n==p1 				->sf==(1+p1)				?
2)n==p1*p1			->sf==(1+p1+p1^2) 	?
3)n==p1*p2			->sf==(p1+1)*(p2+1) ?

*/
	if (1 + n == target_sf)
	{
		while (i++ < sqrt_primes)
		{
			k = 0;
			while (!(n % p->prime))
			{
				n /= p->prime;
				k++;
			}
			if (k)
			{
				pp = p->pp + (k - 1);
				if (ModU64(&target_sf, pp->sumfact))
					return FALSE;
				if (n == 1)
				{
					if (target_sf == 1)
						return TRUE;
					else
						return FALSE;
				}
			}
			p++;
		}
		if (1 + n == target_sf)
			return TRUE;
		else
			return FALSE;
	}

	k  =Sqrt(n);
	if (k * k == n)
	{
		if (1 + k + n == target_sf)
			return TRUE;
		else
			return FALSE;
	}
	else
	{
// n==p1*p2 -> sf==(p1+1)*(p2+1) ?	where p1 != 1 && p2 != 1
		// if p1==1 || p2==1, it is FALSE because we checked a single prime above.

		// sf==(p1+1)*(n/p1+1)
		// sf==n+p1+n/p1+1
		// sf*p1==n*p1+p1^2+n+p1
		// p1^2+(n+1-sf)*p1+n=0
		// x=(-b+/-sqrt(b^2-4ac))/2a
		// a=1
		// x=(-b+/-sqrt(b^2-4c))/2
		// b=n+1-sf;c=n
		b = n + 1 - target_sf;
// x=(-b+/-sqrt(b^2-4n))/2
		disc = b * b - 4 * n;
		if (disc < 0)
			return FALSE;
		x1 = (-b - Sqrt(disc)) / 2;
		if (x1 <= 1)
			return FALSE;
		x2 = n  / x1;
		if (x2 > 1 && x1 * x2 == n)
			return TRUE;
		else
			return FALSE;
	}
}

U0 PutFactors(I64 n) //For debugging
{
	I64 i, k, sqrt = Ceil(Sqrt(n));
	for (i = 2; i <= sqrt; i++)
	{
		k = 0;
		while (!(n % i))
		{
			k++;
			n /= i;
		}
		if (k)
		{
			"%d", i;
			if (k > 1)
				"^%d", k;
			'' CH_SPACE;
		}
	}
	if (n != 1)
		"%d ", n;
}

class RangeJob
{
	CDoc	*doc;
	I64		 num, lo, hi, N, sqrt_primes, cbrt_primes;
	Prime	*primes;
	CJob	*cmd;

} rj[mp_count];

I64 TestCoreSubRange(RangeJob *r)
{
	I64			 i, j, m, n, n2, sf, res = 0, range = r->hi - r->lo, 
				*sumfacts	= MAlloc(range * sizeof(I64)), 
				*residue	= MAlloc(range * sizeof(I64));
	U16			*pow_count	= MAlloc(range * sizeof(U16));
	Prime		*p = r->primes;
	PowPrime	*pp;

	MemSetI64(sumfacts, 1, range);
	for (n = r->lo; n < r->hi; n++)
		residue[n - r->lo] = n;
	for (j = 0; j <r->sqrt_primes; j++)
	{
		MemSet(pow_count, 0, range * sizeof(U16));
		m = 1;
		for (i = 0; i < p->pow_count; i++)
		{
			m *= p->prime;
			n = m - r->lo % m;
			while (n < range)
			{
				pow_count[n]++;
				n += m;
			}
		}
		for (n = 0; n < range; n++)
			if (i = pow_count[n])
			{
				pp = &p->pp[i - 1];
				sumfacts[n] *= pp->sumfact;
				residue [n] /= pp->n;
			}
		p++;
	}

	for (n = 0; n < range; n++)
		if (residue[n] != 1)
			sumfacts[n] *= 1 + residue[n];

	for (n = r->lo; n < r->hi; n++)
	{
		sf = sumfacts[n - r->lo];
		n2 = sf - n - 1;
		if (n < n2 < r->N)
		{
			if (r->lo <= n2<r->hi && sumfacts[n2 - r->lo] - n2 - 1 == n ||
				TestSumFact(n2, sf, r->sqrt_primes, r->cbrt_primes, r->primes))
			{
				DocPrint(r->doc, "%u:%u\n", n, sf - n - 1);
				res++;
			}
		}
	}
	Free(pow_count);
	Free(residue);
	Free(sumfacts);

	return res;
}

#define CORE_SUB_RANGE	0x1000

I64 TestCoreRange(RangeJob *r)
{
	I64		 i, n, res = 0;
	RangeJob rj;

	MemCopy(&rj, r, sizeof(RangeJob));
	for (i = r->lo; i < r->hi; i += CORE_SUB_RANGE)
	{
		rj.lo = i;
		rj.hi = i + CORE_SUB_RANGE;
		if (rj.hi > r->hi)
			rj.hi = r->hi;
		res += TestCoreSubRange(&rj);

		n = rj.hi - rj.lo;
		lock {progress1 += n;}

		Yield;
	}

	return res;
}

I64 MagicPairs(I64 N)
{
	F64			 t0 = tS;
	I64			 res = 0;
	I64			 sqrt_primes, cbrt_primes, num_powprimes, 
				 i, k, n = (N - 1) / mp_count + 1;
	Prime		*primes = PrimesNew(N, &sqrt_primes, &cbrt_primes);
	PowPrime	*powprimes = PowPrimesNew(N, sqrt_primes, primes, &num_powprimes);

	"N:%u SqrtPrimes:%u CbrtPrimes:%u PowersOfPrimes:%u\n", N, sqrt_primes, cbrt_primes, num_powprimes;
	progress1 = 0;
	*progress1_desc = 0;
	progress1_max = N;
	k = 2;
	for (i = 0; i < mp_count; i++)
	{
		rj[i].doc			= DocPut;
		rj[i].num			= i;
		rj[i].lo			= k;
		k += n;
		if (k > N)
			k = N;
		rj[i].hi			= k;
		rj[i].N				= N;
		rj[i].sqrt_primes	= sqrt_primes;
		rj[i].cbrt_primes	= cbrt_primes;
		rj[i].primes		= primes;
		rj[i].cmd			= JobQueue(&TestCoreRange, &rj[i], mp_count - 1 - i, 0);
	}
	for (i = 0; i < mp_count; i++)
		res += JobResGet(rj[i].cmd);
	Free(powprimes);
	Free(primes);
	"Found:%u Time:%9.4f\n", res, tS - t0;
	progress1 = progress1_max = 0;

	return res;
}

MagicPairs(1000000);
