同余问题题集

青蛙的约会(解不定方程)

设走了t步,则第一只青蛙跳了x+mt,第二只是y + nt

则有x +mt \equiv (y + nt) (mod L)

那么有x +mt - (y + nt) = kL

所以可以列出不定方程(m-n)t - Lk = y - x 其中t和k为未知数。

用拓展欧几里得求解。

求完之后判断一下有没有解

然后x乘上K /gcd就是真正的解

然后b /gcd可以作为模,把x调整成最小正整数解

最后注意开long long,数论题大多都要开long long

#include<bits/stdc++.h>
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;

void exgcd(ll a, ll b, ll& d, ll& x, ll& y)
{
	if(!b) { d = a; x = 1; y = 0; }
	else { exgcd(b, a % b, d, y, x); y -= x * (a / b); }
}
 
int main()
{
	ll xx, yy, n, m, L;
	scanf("%lld%lld%lld%lld%lld", &xx, &yy, &m, &n, &L);
	
	ll A, B, K, d, x, y;
	A = m - n, B = -L, K = yy - xx;
	exgcd(A, B, d, x, y);
	
	if(K % d != 0) { puts("Impossible"); return 0; }
	x *= K / d; ll MOD = B / d;
	printf("%lld\n", (x % MOD + MOD) % MOD);
	
	return 0;
}

「NOIP2012」同余方程

转化成ax+by=1即可

#include<bits/stdc++.h>
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;

void exgcd(ll a, ll b, ll& d, ll& x, ll& y)
{
	if(!b) { d = a; x = 1; y = 0; }
	else { exgcd(b, a % b, d, y, x); y -= x * (a / b); }
}
 
int main()
{
	ll a, b, d, x, y;
	scanf("%lld%lld", &a, &b);
	exgcd(a, b, d, x, y);
	
	ll MOD = b / d;
	printf("%lld\n", (x % MOD + MOD) % MOD);
	
	return 0;
}

Sumdiv(乘法逆元)

这是一道非常棒的题目,考到了很多的知识点

(1)质因数分解,快速幂模板熟练

(2)如果对a^b进行质因数分解,那么a照常分解,最后c[i],也就是质因数的幂乘以b就好了

(3) 这道题用到了逆元。逆元很类似倒数这个概念,只不过是在modp的条件下。

逆元可以干嘛呢?当遇到a/b时,可以把除以b变成乘以b的逆元(一般加减乘都可以直接模,除就用逆元),但是b和p要互质,当p为质数的时候,等价于b不是p的倍数,即b%p!=0

如果等于的话可以特判一下。用b^(p-2)求逆元

#include<bits/stdc++.h>
#define cal(a, b) a = (a * b) % MOD
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
const int MOD = 9901;
ll P[20], c[20], m;

void divide(ll a)
{
	for(int i = 2; (ll)i * i <= a; i++)
		if(a % i == 0)
		{
			P[m] = i;
			while(a % i == 0)
			{
				a /= i;
				c[m]++;
			}
			m++;
		}
	if(a > 1) P[m] = a, c[m++] = 1;
}

ll power(ll a, ll b)
{
	ll res = 1 % MOD; a %= MOD;
	for(; b; b >>= 1)
	{
		if(b & 1) cal(res, a);
		cal(a, a);
	}
	return res;
}
 
int main()
{
	ll a, b;
	scanf("%lld%lld", &a, &b);
	divide(a);
	
	ll ans = 1;
	REP(i, 0, m)
	{
		ll p = P[i], n = c[i] * b + 1;
		if((p - 1) % MOD == 0)
		{
			ans = n % MOD * ans % MOD;
			continue;
		}
		
		ll t = power(p, n);
		t = (t - 1 + MOD) % MOD;
		cal(ans, t); 
		cal(ans, power(p - 1, MOD - 2));
	}
	printf("%lld\n", ans);
	
	return 0;
}

顺便补充一下线性求逆的代码

void get_inv()
{
	inv[1] = 1;
	REP(i, 2, MAXN)
		inv[i] = (p - p / i) * inv[p % i] % p;
}

曹冲养猪(同余方程)

中国剩余定理还是比较好用的,代码量很小,记住套路就好了。

#include<bits/stdc++.h>
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
const int MAXN = 15;
ll m[MAXN], a[MAXN];

void exgcd(ll a, ll b, ll& d, ll& x, ll& y)
{
	if(!b) { d = a; x = 1; y = 0; return; }
	else { exgcd(b, a % b, d, y, x); y -= x * (a / b); }
}

int main()
{
	int n;
	scanf("%d", &n);
	
	ll M = 1;
	REP(i, 0, n)
	{
		scanf("%lld%lld", &m[i], &a[i]);
		M *= m[i];
	}
	
	ll ans = 0, d, x, y;
	REP(i, 0, n)
	{
		ll mi = M / m[i];
		exgcd(mi, m[i], d, x, y);
		ans = (ans + x * mi * a[i]) % M;
	} 
	ans = (ans % M + M) % M;
	printf("%lld\n", ans);
	
    return 0;
}

同余方程解法

#include<bits/stdc++.h>
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;

void exgcd(ll a, ll b, ll& d, ll& x, ll& y)
{
	if(!b) { d = a; x = 1; y = 0; return; }
	else { exgcd(b, a % b, d, y, x); y -= x * (a / b); }
}

int main()
{
	ll n, b1, m1, b2, m2;
	scanf("%lld%lld%lld", &n, &m1, &b1);
	
	REP(i, 1, n)
	{
		scanf("%lld%lld", &m2, &b2);
		ll A, B, K, x, y, d;
		A = m1, B = m2, K = b2 - b1;
		exgcd(A, B, d, x, y);	
		
		x *= K / d; int mod = B / d;
		x = (x % mod + mod) % mod;
		
		b1 = m1 * x + b1; 
		m1 = m1 / d * m2;  
	} 
	printf("%lld\n", b1);
	
	return 0;
}
 

Strange Way to Express Integers (同余方程组)

和上一题没多大区别

#include<bits/stdc++.h>
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;

void exgcd(ll a, ll b, ll& d, ll& x, ll& y)
{
	if(!b) { d = a; x = 1; y = 0; return; }
	else { exgcd(b, a % b, d, y, x); y -= x * (a / b); }
}

int main()
{
	ll n, b1, m1, b2, m2;
	while(~scanf("%lld", &n))
	{
		scanf("%lld%lld", &m1, &b1);
		bool ok = true;
		
		REP(i, 1, n)
		{
			scanf("%lld%lld", &m2, &b2);
			ll A, B, K, x, y, d;
			A = m1, B = m2, K = b2 - b1;
			exgcd(A, B, d, x, y);	
		
			if(K % d != 0) ok = false;
			x *= K / d; int mod = B / d;
			x = (x % mod + mod) % mod;
		
			b1 = m1 * x + b1; 
			m1 = m1 / d * m2;  
		} 
	
		if(!ok) puts("-1");
		else printf("%lld\n", b1);
	}
	
	return 0;
}
 

Biorhythms (同余方程)

同样,中国剩余定理我都用同余方程做……

比较奇怪的是,第一个模数如果是正的话就会WA掉18分

要写为负。

我也不知道为什么

#include<bits/stdc++.h>
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std;

void exgcd(int a, int b, int& d, int& x, int& y)
{
	if(!b) { d = a; x = 1; y = 0; return; }
	else { exgcd(b, a % b, d, y, x); y -= x * (a / b); }
}

int main()
{
    int p, e, i, D, kase = 0;
	while(~scanf("%d%d%d%d", &p, &e, &i, &D))
	{
		if(p == -1) break;
		int A, B, K, d, x, y;
		A = 23, B = -28, K = e - p;
		exgcd(A, B, d, x, y);
		
		x *= (K / d); int mod = B / d;
		x = (x % mod + mod) % mod;
		
		int b = 23 * x + p;
		int m = 23 * 28;
		
		A = m, B = 33, K = i - b;
		exgcd(A, B, d, x, y);	
		x *= (K / d); mod = B / d;
		x = (x % mod + mod) % mod;
		b = m * x + b;
		
		int ans = b - D <= 0 ? b - D + 21252 : b - D;
		printf("Case %d: the next triple peak occurs in %d days.\n", ++kase, ans);
	}
	
	return 0;
}

bzoj 2242

这道题写了好久……

学了一个新的算法,高次同余方程的解法。

可以参考这位大牛的写法大牛博客

注意几个点

(1)输入的时候可以直接将a取模。

(2)注意什么时候不成立。对于高次同余方程a^x = b (mod p)

将a和b对p取模之后,如果a为0,b为0,则x = 0, 如果a 为 0, b不为0,则无解

(3)BSGS算法中m是向上取整,即m = ceil(sqrt(p))

#include<bits/stdc++.h>
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
ll a, b, p;

ll power(ll a, ll b, ll p)
{
	ll res = 1 % p; a %= p;
	for(; b; b >>= 1)
	{
		if(b & 1) res = res * a % p;
		a = a * a % p;
	}
	return res;
}

void solve1()
{
	printf("%lld\n", power(a, b, p));
}

ll gcd(ll a, ll b) { return !b ? a : gcd(b, a % b); } 

inline ll inv(ll a) { return power(a, p - 2, p); }

void solve2()
{
	if(gcd(a, p) != 1) puts("Orz, I cannot find x!");
	else printf("%lld\n", b * inv(a) % p);
}

map<ll, ll> mp;
void solve3()
{
	mp.clear();
	b %= p; a %= p;
	
	if(a == 0 && b == 0) { puts("1"); return; } 
	if(a == 0 && b != 0) { puts("Orz, I cannot find x!"); return; } 
	
	ll m = ceil(sqrt(p));  //注意这里向上取整 
	mp[b] = m + 1;
	ll sum = 1, t = inv(a);

	REP(i, 1, m)
	{
		sum = sum * t % p;
		ll q = b * sum % p;
		if(!mp[q]) mp[q] = i;
	}
	
	t = power(a, m, p); sum = 1;
	REP(i, 0, m)
	{
		if(mp[sum])
		{
			if(mp[sum] == m + 1) printf("%lld\n", i * m);
			else printf("%lld\n", i * m + mp[sum]);
			return;
		}
		sum = sum * t % p;
	}
	puts("Orz, I cannot find x!");
}

int main()
{
    int T, k;
    while(~scanf("%d%d", &T, &k))
    {
    	while(T--)
    	{
    		scanf("%lld%lld%lld", &a, &b, &p);
    		if(k == 1) solve1();
    		if(k == 2) solve2();
    		if(k == 3) solve3();
		}
	}
	return 0;
}

五指山

x = p, y = q

q = p + dx(mod n)

可以推出dx-ny = q - p

exgcd解方程就好了。

注意真正实现的时候y的系数是n,不是-n

不然x最后输出可能是负的。

#include<bits/stdc++.h>
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;

void exgcd(ll a, ll b, ll& d, ll& x, ll& y)
{
	if(!b) { d = a; x = 1; y = 0; return; }
	else { exgcd(b, a % b, d, y, x); y -= x * (a / b); }
}

int main()
{
	int T;
	scanf("%d", &T);
	
	while(T--)
	{
		ll p, q, x, y, d, n, D;
		scanf("%lld%lld%lld%lld", &n, &D, &p, &q);
		
		ll A = D, B = n, K = q - p;
		exgcd(A, B, d, x, y);
		
		if(K % d != 0) { puts("Impossible"); continue; }
		x *= K / d; ll mod = B / d;
		x = (x % mod + mod) % mod;
		
		printf("%lld\n", x);
	}
	
	return 0;
}

C Looooops

和上一题差不多

A+Cx\equiv B(mod 2^{k})

Cx+2^{k}y=B-A

#include<bits/stdc++.h>
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
ll p[40], x, y, d, a, b, c, k;

void exgcd(ll a, ll b, ll& d, ll& x, ll& y)
{
	if(!b) { d = a; x = 1; y = 0; return; }
	else { exgcd(b, a % b, d, y, x); y -= x * (a / b); }
}

int main()
{
	p[0] = 1;
	_for(i, 1, 32) p[i] = p[i-1] << 1;
	
	while(~scanf("%lld%lld%lld%lld", &a, &b, &c, &k))
	{
		if(a == 0 && b == 0 && c == 0 && k == 0) break;
		ll A = c, B = p[k], K = b - a;
		exgcd(A, B, d, x, y);
		
		if(K % d != 0) { puts("FOREVER"); continue; }
		x *= K / d; ll mod = B / d;
		x = (x % mod + mod) % mod;
		
		printf("%lld\n", x);
	}
	
	return 0;
}

[Noi2002]Savage

我竟然做出了noi的题,感动!!(虽然WA了几次)

我第一眼以为是二分洞穴的数目来做。

设当前洞穴数为p,对于当前两个人来说

c1+p1x\equiv c2+p2x(mod p)

化简得(p1-p2)x+py=c2-c1

解方程就好了,但这里有个很重要的细节

Ax + By = K

即使B为正,如果A为负或者K为负会导致gcd为负,最终算出来的x可能为负

所以最后加上一句 if(x < 0) x += abs(t);

	x *= K / d; ll t = B / d;
	x = (x % t + t) % t;
	if(x < 0) x += abs(t);

 

如果无解,当前p就成立。当x大于l1或者大于l2时p也成立(还没走到一起就有人死了)

因为看到n<=15,所以果断暴力判断每两个人在当前洞穴数下

会不会到一个洞穴。

然后交上去WA,30分

然后我想了想,貌似不满足单调性……(其实可以打表出来看一下)

那我就非常非常暴力地枚举p吧,从1到1e6

交上去

哇靠90分,竟然不会超时??

有一个点WA

然后发现p最小是cmax,不是0

然后交了

AC!!!!

#include<bits/stdc++.h>
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std;

const int MAXN = 20;
typedef long long ll;
ll c[MAXN], p[MAXN], l[MAXN];
int n;

void exgcd(ll a, ll b, ll& d, ll& x, ll& y)
{
	if(!b) { d = a; x = 1; y = 0; return; }
	else { exgcd(b, a % b, d, y, x); y -= x * (a / b); }
}

bool judge(int i, int j, ll mod)
{
	ll A = p[i] - p[j], B = mod, K = c[j] - c[i], d, x, y; 
	exgcd(A, B, d, x, y);
	if(K % d != 0) return true;
	
	x *= K / d; ll t = B / d;
	x = (x % t + t) % t;
	if(x < 0) x += abs(t);

	return x > l[i] || x > l[j];
}

bool check(ll mod)
{
	REP(i, 0, n)
		REP(j, i + 1, n)
			if(!judge(i, j, mod))
				return false;
	return true;
}

int main()
{
	ll maxt = 0;
	scanf("%d", &n);
	REP(i, 0, n) 
	{
		scanf("%lld%lld%lld", &c[i], &p[i], &l[i]);
		maxt = max(maxt, c[i]);
	}
	
	ll ans = maxt;
	while(ans <= 1e6)
	{
		if(check(ans)) break;
		ans++;
	}	
	printf("%lld\n", ans);
	 
	return 0;
}

 

 

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值