中国剩余定理(普通和拓展)

类型:

举个例子,如果建了 3 个猪圈,剩下 1 头猪没有地方安家。如果建造了 5 个猪圈,但是仍然有 1 头猪没有地方去,然后如果建造了 7 个猪圈,还有 2 头没有地方去。求:有多少头猪?

中国剩余定理 (注意:这里的模数两两互质

然后根据步骤运算就可以了,其中用到了extend_gcd(ps:这个真的好常用啊!) 

模板:

LL exgcd(LL a,LL b,LL &x,LL &y){
  if(b==0){x=1, y=0; return a;}
  LL d, x1, y1;
  d = exgcd(b, a%b, x1, y1);
  x = y1, y = x1-a/b*y1;
  return d;
}
LL CRT(LL m[], LL r[]){
  LL M = 1, ans = 0;
  for(int i=1;i<=n;i++) M*=m[i];//对应第一步
  for(int i=1; i<=n; i++)
  {
    LL c = M/m[i], x, y;//对应第二步
    exgcd(c, m[i], x, y);//对应第三步
    ans = (ans+r[i]*c*x%M)%M;//对应第四步
  }
  return (ans%M + M)%M;//最小正整数解
}

题解:

#include<iostream>
using namespace std;
typedef long long ll;
ll a[10];
ll b[10];
int N;
ll sum = 1;
ll extend_gcd(ll a, ll b, ll &x, ll &y)
{
	if (b == 0)
	{
		x = 1;
		y = 0;
		return a;
	}
	else
	{
		ll r=extend_gcd(b, a % b, x, y);
		ll temp = x;
		x = y;
		y = temp - a / b * y;
		return r;
	}
}
ll china(ll m[], ll n[])
{
	ll ans = 0;
	for (int i = 0; i < N; i++)
	{
		ll t = sum / m[i];
		ll x, y;
		extend_gcd(t, m[i], x, y);
		ans = (ans + n[i] * t * x % sum) % sum;
	}
	return (ans%sum+sum) % sum;
}
int main()
{
	cin >> N;
	for (int i = 0; i < N; i++)
	{
		cin >> a[i] >> b[i];
		sum *= a[i];
	}
	cout << china(a, b) << endl;
	return 0;
}

那么,当模数不再两两互质呢?

除此之外:对于 100% 的数据,1≤n≤10^5,1≤bi,ai≤10^12,保证所有 ai 的最小公倍数不超过 10^18;

在这种情况下,由于两两不再互质,那么中国剩余定理的第三步——求ci在mi下的逆元,公式为c_{i}*x+m_{i}*y=gcd(c_{i},m_{i})在这里,由于c_{i}=m_{i}/M,又不再互质了,所以gcd最后可能等于0,其逆元自然不存在了。

因此,拓展中国剩余定理应运而出:

 所以,过程其实就是进行n-1次方程合并(求p,再通过p求新r,在再求新m)

代码如下:

#include<iostream>
using namespace std;
typedef __int128 ll;
int n;
ll a[100001], b[100001];
ll exdgcd(ll a, ll b, ll& x, ll& y)
{
	if (b == 0)
	{
		x = 1;
		y = 0;
		return a;
	}
    ll t = exdgcd(b, a % b, x, y);
	ll temp = x;
	x = y;
	y = temp - a / b * y;
	return t;
}
ll CRT(ll* a, ll* b)
{
	ll m1 = a[0],r1=b[0];
	ll x, y;
	for (int i = 1; i < n; i++)
	{
		ll m2 = a[i], r2 = b[i];
		ll tempm = exdgcd(m1, m2, x, y);
		x = (x * (r2 - r1) / tempm);
		x = (x % (m2 / tempm) + m2 / tempm) % (m2 / tempm);
		r1 = (m1 * x + r1);
		m1 = (m1 * m2 / tempm);
	}
	return (r1 % m1 + m1) % m1;
}
int main()
{
	scanf("%lld",&n);
	for (int i = 0; i < n; i++)
	{
		scanf("%lld%lld",&a[i],&b[i]);
	}
	printf("%lld",CRT(a,b));
    return 0;
}

 在这道题我用了__int128,这样拓展到了39位,就不用担心数据溢出了。当然,如果不这用,用高精度也是一样的。

但是他高精度的使用我没有看很懂,因为他是用另两个long long型的数相乘再进行取模,所以每一次进行乘法都要进行取模,有点麻烦:

ll mul(ll a,ll b,ll p)//高精度乘法
{
	ll ans=0,x=a;
	while(b!=0)
	{
		if(b&1)//是否为奇数
		{
			ans=(ans+x)%p;//单独加1个x
		}
		x=(x+x)%p;//x乘以2
		b>>=1;//除以二
	}
	return (ans%p+p)%p;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值