CodeForces 1499D exgcd 因式分解

原题链接

题意

  • 给我们三个1e7以内的数 c , d , x c, d, x c,d,x,要求我们求出有多少对 a , b a, b a,b,满足

c ⋅ l c m ( a , b ) − d ⋅ g c d ( a , b ) = x c \cdot lcm(a, b) - d \cdot gcd(a, b) = x clcm(a,b)dgcd(a,b)=x

思路

  • 这里给出一种和题解不同(但本质上应该也是相似的)的一种解法。首先看到这个二元一次方程我们马上就可以想到扩展欧几里得。

  • 这样我们用exgcd拿到关于lcm与gcd的正整数通解之后,还要考虑什么时候这两个数能组成一对真正的lcm与gcd

  • 这里我们的通解是

l c m = X + n ∗ c 1 g c d = Y + n ∗ c 2 lcm = X + n * c_1 \\ gcd = Y + n * c_2 lcm=X+nc1gcd=Y+nc2

  • 其中 c 1 c_1 c1 d / g c d ( c , d ) d / gcd(c, d) d/gcd(c,d), c 2 c_2 c2 c / g c d ( c , d ) c / gcd(c, d) c/gcd(c,d)

  • 然后显然gcd必须整除lcm,这样才能找到a与b,这个可以写为

X + n ∗ c 1 Y + n ∗ c 2 = k ( k ∈ N + ) \frac{X + n * c_1}{Y + n * c_2} = k \quad (k \in N^+) Y+nc2X+nc1=k(kN+)

  • 可以写成下列二元二次方程的形式

X + n ∗ c 1 = k Y + k ∗ n ∗ c 2 X + n * c_1 = kY + k * n * c_2 X+nc1=kY+knc2

  • 显然找到这个方程的所有正整数解(n与k必须都是正整数)就能找到所有的a,b了,我们可以使用因式分解法来解决这个方程

( c 2 ∗ k − c 1 ) ( c 2 ∗ n + Y ) = c 2 ∗ x − c 1 ∗ y (c_2 * k - c_1)(c_2 * n + Y) = c_2 * x - c_1 * y (c2kc1)(c2n+Y)=c2xc1y

  • 方程右边是常数,所以我们对右边这个数因式分解,然后就能够得到左边的全部整数解,注意这个过程中要保证n与k都是正整数,否则跳过。

  • 拿到了一对n和k,我们可以直接算出 l c m lcm lcm g c d gcd gcd,然后对于这一对 l c m lcm lcm g c d gcd gcd, ( a , b ) (a,b) (ab)数对的数量其实就是 2 ( l c m / g c d ) 的 质 因 数 种 类 数 2^{(lcm / gcd) 的质因数种类数} 2(lcm/gcd),因为 l c m / g c d = = ( a / g c d ) ∗ ( b / g c d ) lcm / gcd == (a / gcd) * (b / gcd) lcm/gcd==(a/gcd)(b/gcd), 而我们知道 ( a / g c d ) (a / gcd) (a/gcd) ( b / g c d ) (b / gcd) (b/gcd)是没有公因数的,所以也就是说将 l c m / g c d lcm / gcd lcm/gcd的几种不同的质因数进行任意分配,而每种质因数都可能被分到a或者b,所以对答案的贡献是

2 ( l c m / g c d ) 的 质 因 数 种 类 数 2^{(lcm / gcd) 的质因数种类数} 2(lcm/gcd)

  • 另外这个质因数种类数不能直接算,否则会t,不过我们可以线性预处理出2e7(注意上界是2e7不是1e7,可以从lcm的计算公式上得出)之内每个数的质因数种类数(记为 c n t i cnt_i cnti),具体方法是

    • 对于当前 i i i,如果是质数则 c n t i = 1 cnt_i = 1 cnti=1

    • 否则 c n t i = c n t i / m i n p r i m e i cnt_i = cnt_{i / minprime_i} cnti=cnti/minprimei m i n p r i m e i minprime_i minprimei是指 i i i的最小质因数,另外,如果 i % m i n p r i m e i 2 ! = 0 i \% minprime_i^2 != 0 i%minprimei2!=0,还要让 c n t i + = 1 cnt_i += 1 cnti+=1,因为这代表 i i i i / m i n p r i m e i i / minprime_i i/minprimei多了一个质因数 i i i

    • 以上计算过程可以在欧拉筛的同时进行计算,因为欧拉筛使用到了最小质因数

AC代码
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

long long prime[20000005], co = 0;
bool iprime[20000005] = {0};
long long cntp[20000005] = {0};

long long qpow(long long a, long long b)
{
	long long c = 1;
	while (b)
	{
		if (b & 1)
		{
			c *= a;
		}
		a *= a;
		b >>= 1;
	}
	return c;
}

void euler()
{
	iprime[1] = true;
	for (int i = 2; i <= 20000000; ++i)
	{
		if (!iprime[i])
		{
			prime[++co] = i;
			cntp[i] = 1;
		}
		for (int j = 1; j <= co && i * prime[j] <= 20000000; ++j)
		{
			iprime[i * prime[j]] = true;
			cntp[i * prime[j]] = cntp[i];
			if (i % prime[j] == 0)
			{
				break;
			}
			++cntp[i * prime[j]];
		}
	}
}

long long gcd(long long a, long long b)
{
	if (!b)
	{
		return a;
	}
	else
	{
		return gcd(b, a % b);
	}
}

long long exgcd(long long a, long long b, long long& xx, long long& yy)
{
	if (!b)
	{
		xx = 1;
		yy = 0;
		return a;
	}
	else
	{
		long long r = exgcd(b, a % b, xx, yy);
		long long t = xx;
		xx = yy;
		yy = t - yy * (a / b);
		return r;
	}
}

long long calc(long long a, long long b, long long c)
{
    long long xx, yy;
    long long d = exgcd(a, b, xx, yy);
	if (c - c / d * d == 0)
	{
		c /= d;
		xx *= c;
		b /= d;
		if (b < 0)
		{
			b = -b;
		}
		xx %= b;
		if (xx < 0)
		{
			xx += b;
		}
		return xx;
	}
	else
	{
		return -1;
	}
}

int t;
long long c, d, x, ans;

int main()
{
	euler();
	scanf("%d", &t);
	while (t--)
	{
		scanf("%lld%lld%lld", &c, &d, &x);
		long long xx = calc(c, d, x);
		if (xx == -1)
		{
			printf("0\n");
			continue;
		}
		long long dd = gcd(c, d);
		long long c1 = d / dd;
		long long c2 = c / dd;
		long long yy = -(x - xx * c) / d;
		if (yy <= 0)
		{
			xx += c1 * (-yy / c2 + 1);
			yy += c2 * (-yy / c2 + 1);
		}
		
		ans = 0;
		long long p = xx * c2 - yy * c1;
		for (long long i = 1; i * i <= p; ++i)
		{
			if (p % i == 0)
			{
				if (i >= yy && (i - yy) % c2 == 0 && (p / i + c1) % c2 == 0)
				{
					long long ln = (i - yy) / c2;
					ans += qpow(2, cntp[(xx + ln * c1) / (yy + ln * c2)]);
				}
				if (i * i == p)
				{
					continue;
				}
				if (p / i >= yy && (p / i - yy) % c2 == 0 && (i + c1) % c2 == 0)
				{
					long long ln = (p / i - yy) / c2;
					ans += qpow(2, cntp[(xx + ln * c1) / (yy + ln * c2)]);
				}
			}
		}
		printf("%lld\n", ans);
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值