扩展中国剩余定理

废话

这玩意跟中国剩余定理没有半毛钱关系,所以所以没学过中国剩余定理的完全不用担心。

当然还是建议先学一学中国剩余定理

介绍

这是一个用来求形如下面这样的同余方程组的解的算法:
{ x ≡ a 1 ( m o d m 1 ) x ≡ a 2 ( m o d m 2 )    ⋮ x ≡ a n ( m o d m n ) \begin{cases} x \equiv a_1 \pmod {m_1}\\ x \equiv a_2 \pmod {m_2}\\ ~~\vdots\\ x \equiv a_n \pmod {m_n}\\ \end{cases} xa1(modm1)xa2(modm2)  xan(modmn)

其中, m 1 , m 2 , . . . , m n m_1,m_2,...,m_n m1,m2,...,mn 之间不一定互质。

做法

很神奇,网上有两个版本,有两种不同的做法。(并不知道哪一个是正版……)

做法 1 1 1

假设我们已经求出了前 p p p 个方程组的解 x 1 x_1 x1

不妨设 M = ∏ i = 1 p m i M=\prod\limits_{i=1}^p {m_i} M=i=1pmi,那么前 p p p 个方程组的通解为 x 1 + k M    ( k ∈ Z ) x_1+kM~~(k\in Z) x1+kM  (kZ)

那么要满足第 p + 1 p+1 p+1 个方程,相当于要找到一个合法的 k k k,满足:
x 1 + k M ≡ a p + 1 ( m o d m p + 1 ) x_1+kM \equiv a_{p+1} \pmod {m_{p+1}} x1+kMap+1(modmp+1)

相当于
x 1 + k M + L m p + 1 = a p + 1    ( L ∈ Z ) ( m o d m p + 1 ) x_1+kM+Lm_{p+1} = a_{p+1}~~(L\in Z) \pmod {m_{p+1}} x1+kM+Lmp+1=ap+1  (LZ)(modmp+1)

移项得
k M + L m p + 1 = a p + 1 − x 1 ( m o d m p + 1 ) kM+Lm_{p+1} = a_{p+1}-x_1 \pmod {m_{p+1}} kM+Lmp+1=ap+1x1(modmp+1)

用扩展欧几里得算法求出 k , L k,L k,L 即可,那么对于前 p + 1 p+1 p+1 个方程的解就是 x 1 + k M x_1+kM x1+kM

但是这种做法有一个限制,就是 M M M 要在 l o n g   l o n g long~long long long 范围内,而有些恶心题就不保证。

有一个小优化就是稍稍变一下 M M M 的定义,让 M = l c m i = 1 p m i M=lcm_{i=1}^p {m_i} M=lcmi=1pmi,这样可以稍稍减小一点 M M M

模板题传送门

代码如下:

#include <cstdio>
#include <cstring>
#define ll long long
#define maxn 100010

int n;
ll exgcd(ll a,ll b,ll &x,ll &y)
{
    if(b==0)return x=1,y=0,a;
    ll d=exgcd(b,a%b,y,x);
    return y-=a/b*x,d;
}
ll a[maxn],m[maxn];
ll ksc(ll x,ll y,ll mod)
{
	ll re=0,tot=x;
	while(y)
	{
		if(y&1ll)re=(re+tot)%mod;
		tot=(tot+tot)%mod;
		y>>=1;
	}
	return re;
}
ll excrt()
{
	ll M=m[1],x=a[1];
	for(int i=2;i<=n;i++)
	{
		ll k,L,c=(a[i]-x%m[i]+m[i])%m[i];
		ll gcd=exgcd(M,m[i],k,L),bg=m[i]/gcd;
		k=ksc(k,c/gcd,bg);
		x+=k*M;
		M*=bg;
		x=(x%M+M)%M;
	}
	return x;
}

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	scanf("%lld %lld",&m[i],&a[i]);
	printf("%lld\n",excrt());
}

做法 2 2 2

这种做法就是大力将两个同余式和并为一个,然后大力合并所有同余式。

假设现在有两个同余式:
{ x ≡ a 1 ( m o d m 1 ) x ≡ a 2 ( m o d m 2 ) \begin{cases} x\equiv a_1 \pmod {m_1}\\ x\equiv a_2 \pmod {m_2} \end{cases} {xa1(modm1)xa2(modm2)

那么有
{ x = a 1 + k 1 m 1 x = a 2 + k 2 m 2 \begin{cases} x=a_1+k_1m_1\\ x=a_2+k_2m_2\\ \end{cases} {x=a1+k1m1x=a2+k2m2

则有
a 1 + k 1 m 1 = a 2 + k 2 m 2 k 1 m 1 = a 2 − a 1 + k 2 m 2 \begin{aligned} a_1+k_1m_1&=a_2+k_2m_2\\ k_1m_1&=a_2-a_1+k_2m_2 \end{aligned} a1+k1m1k1m1=a2+k2m2=a2a1+k2m2

g = gcd ⁡ ( m 1 , m 2 ) g=\gcd(m_1,m_2) g=gcd(m1,m2),将柿子除以 g g g,则有:
k 1 m 1 g = a 2 − a 1 g + k 2 m 2 g k 1 m 1 g ≡ a 2 − a 1 g ( m o d m 2 g ) k 1 = a 2 − a 1 g × i n v ( m 1 g ) + y × m 2 g \begin{aligned} \frac {k_1m_1} g&=\frac {a_2-a_1} g +\frac {k_2m_2} g\\ \frac {k_1m_1} g&\equiv\frac {a_2-a_1} g \pmod {\frac {m_2} g}\\ k_1&= \frac {a_2-a_1} g \times inv(\frac {m_1} g)+y \times \frac {m_2} g \end{aligned} gk1m1gk1m1k1=ga2a1+gk2m2ga2a1(modgm2)=ga2a1×inv(gm1)+y×gm2

其中, i n v inv inv 是指逆元。

k 1 k_1 k1 带回去,有
x = a 1 + m 1 × ( a 2 − a 1 g × i n v ( m 1 g ) + y × m 2 g ) x = a 1 + m 1 × a 2 − a 1 g × i n v ( m 1 g ) + y × m 1 m 2 g x ≡ a 1 + m 1 × a 2 − a 1 g × i n v ( m 1 g ) ( m o d m 1 m 2 g ) \begin{aligned} x&=a_1+m_1 \times \left(\frac {a_2-a_1} g \times inv(\frac {m_1} g)+y \times \frac {m_2} g \right)\\ x&=a_1+m_1 \times \frac {a_2-a_1} g \times inv(\frac {m_1} g) + y \times \frac {m_1m_2} g\\ x&\equiv a_1+m_1 \times \frac {a_2-a_1} g \times inv(\frac {m_1} g) \pmod {\frac {m_1m_2} g} \end{aligned} xxx=a1+m1×(ga2a1×inv(gm1)+y×gm2)=a1+m1×ga2a1×inv(gm1)+y×gm1m2a1+m1×ga2a1×inv(gm1)(modgm1m2)

于是两条同余式合并成了一条。

但是要注意,里面 a 2 − a 1 g × i n v ( m 1 g ) \frac {a_2-a_1} g \times inv(\frac {m_1} g) ga2a1×inv(gm1) 这一部分依然要在模 m 2 g \frac {m_2} g gm2 意义下计算,但是前面的 × m 1 \times m_1 ×m1 在模 m 1 m 2 g \frac {m_1m_2} g gm1m2 意义下计算。

g g g 不能整除 a 2 − a 1 a_2-a_1 a2a1 时,该方程组无解。

代码如下:

#include <cstdio>
#include <cstring>
#define ll long long
#define maxn 100010

int n;
ll gcd(ll x,ll y){return y==0?x:gcd(y,x%y);}
void exgcd(ll a,ll b,ll &x,ll &y)
{
	if(b==0){x=1,y=0;return;}
	exgcd(b,a%b,y,x);
	y-=a/b*x;
}
ll inv(ll a,ll mod)
{
	ll x,y;
	exgcd(a,mod,x,y);
	return (x%mod+mod)%mod;
}
ll ksc(ll x,ll y,ll mod)
{
	ll re=0,tot=x;
	y=(y%mod+mod)%mod;
	while(y)
	{
		if(y&1ll)re=(re+tot)%mod;
		tot=(tot+tot)%mod;
		y>>=1;
	}
	return re;
}
ll a[maxn],m[maxn];
ll excrt()
{
	ll a1=a[1],m1=m[1],a2,m2,g;
	for(int i=2;i<=n;i++)
	{
		a2=a[i],m2=m[i];
		g=gcd(m1,m2);
		if((a2-a1)%g!=0)return -1;
		a1+=ksc(inv(m1/g,m2/g),(a2-a1)/g,m2/g)%(m2/g)*m1;
		m1*=m2/g;
		a1=(a1%m1+m1)%m1;
	}
	return a1;
}

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	scanf("%lld %lld",&m[i],&a[i]);
	printf("%lld",excrt());
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值