数学渣渣愉快的玩了一把数论,来总结一下几种常用的算法入门,不过鶸也是刚刚入门,
所以也只是粗略的记录下原理,贴下模板,以及入门题目(感受下模板怎么用的)
(PS:文中亮色字体都可以点进去查看百度原文)
附赠数论入门训练专题:点我打开专题(题目顺序基本正常,用以配套数论入门)
一、同余定理
简单粗暴的说就是:若 a-b == m 那么 a%m == b%m
这个模运算性质一眼看出。。。直接上入门水题:
附AC代码(这个也没啥模板。。。。知道就好)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<stdlib.h>
#define mem(a,x) memset(a,x,sizeof(a))
using namespace std;
typedef long long ll;
/*
同余:
if
a-b == m
then
a%m == b%m
*/
const int N = 1000000;
bool vis[N+5];
int a[N+5];
bool ol[N+5];
int main()
{
int T;cin>>T;
while (T--)
{
int n;
scanf("%d",&n);
mem(vis,0);
for (int i = 1;i <= n;++i) scanf("%d",a+i);
for (int i = 1;i <= n;++i)
{
for (int j = i+1;j <= n;++j)
{
int d = abs(a[i]-a[j]);
vis[d] = 1;
}
}
bool fd = 0;
for (int m = 1;!fd;++m)
{
if (!vis[m])
{
mem(ol,0);bool ok = 1;
for (int i = 1;ok&&i <= n;++i)
{
if (ol[a[i]%m]) ok = 0;
else ol[a[i]%m] = 1;
}
if (ok)
{
printf("%d\n",m);
fd = 1;
}
}
}
}
return 0;
}
二、扩展欧几里德算法
这个扩展是从原欧几里德算法扩展而来,这个算法真心非常有用!非常有用!非常有用!(中国剩余定理也要用到它)
首先说欧几里德算法(其实就是我们小时候数学课上就学过的辗转相除法求gcd)
欧几里德说:gcd(a,b) = gcd(b,a%b)
于是得到欧几里德算法:
int gcd(int a,int b)
{
return b==0?a:gcd(b,a%b);
}
该算法可以在几乎是log的时间算a,b的最大公因数gcd
PS:__gcd(a,b)库函数可以直接调用,但是有些OJ上提交会CE
现在,我们令a,b的最大公因数为gcd,那么我们一定可以找到一组x,y满足:(这个是欧几里德的定理一)
a*x + b*y = gcd;
此时,我们设x0,y0是上述不定方程的特解,那么就可以用x0,y0表示出整个不定方程的通解
x = x0 + (b/gcd)*t;
y = y0 - (a/gcd)*t;
然后是怎么求解通解x,y?
倒过去看看欧几里德算法,显然,它的结束条件是 b = 0的时候返回a
这就意味着,终止状态是 :a = gcd ,b = 0;
将这组a,b代会不定方程ax+by=gcd,可以得到一组特解:x = 1,y = 0
找到终止状态,我们再看看递归的过程:
gcd = b*x1 + (a%b)*y1
= b*x1 + (a-(a/b)*b)*y1 // a%b = a-(a/b)*b
= b*x1 + a*y1 - (a/b)*b*y1
= a*y1 + b*(x1-a/b*y1)
最后得到gcd = a*y1 + b*(x1-a/b*y1)和原来的式子gcd = a*x + b*y一对比就得到:
x = y1,y = x1 - a/b*y1;
上面两个等式很重要!!(因为模板里面会直接用到,如果是直接背模板就得把它背熟)
下面就是扩展欧几里德算法:
int e_gcd(int a,int b,int &x,int &y)
{
if (b == 0)
{
x = 1,y = 0;
return a;
}
int ans = e_gcd(b,a%b,x,y);
int tmp = x;
x = y;
y = tmp - a/b*y;
}
这个算法本质上还是求a,b的最大公因数,只是在计算的过程中顺带计算x,y(同时体验了一把引用的神奇)
扩展欧几里德算法有什么用?反正用法很多,它可以求解形如 ax+by=c的通解
但是实际应用中通常指要求在通解中选一些特殊的解,
比如一个数对于另一个数的乘法逆元。那么什么是乘法逆元?
ax ≡ 1(mod b) // ax % b = 1
这里,我们称x是a关于b的乘法逆元
ax%b = 1,可以化成 ax = by + 1
显然,它等价于这样的表达式:ax + by = 1
这个式子很眼熟有木有!!!如果等式右边是gcd(a,b)就好了!!!
然后这里copy欧几里德的三个定理:
定理一:如果d = gcd(a,b),则必能找到正的或负的整数x和y使d = ax + by
定理二:若gcd(a,b) = 1,则方程ax≡c(mod b)在[0,b-1]上有唯一解
定理三:若gcd(a,b) = d,则方程ax≡c(mod b)在[0,b|d01]上有唯一解
于是,对上述方程 ax + by = 1, 当gcd(a,b) != 1的时候无解
故方程ax + by = c 有解的条件是 : c%gcd(a,b) = 0;
按乘法逆元讲,一般,我们能找到无数组解满足条件,但一般题目是求解最小的那组解
假设我们求出了特解x0,那么 ,只需要用x0 % b就是最小解了
为什么?(这个我没管,反正知道是这个。。。。后面贴的参考资料链接里面有。。。)
另外,有的时候求得的特解可能是个负数,或者说b是负数,怎么办?
如果b是负数,取b的绝对值
如果x0是负数,让x0对|b|取模后再加上|b|
然后,直接上模板代码:
int Cal(int a,int b)//求最小的x使ax+ by = 1
{
int x,y;
int gcd = e_gcd(a,b,x,y);
if (1%gcd) return -1;//无解
x*=1/gcd;
b = abs(b);
int ans = x%b;
if (ans <= 0) ans += b;
return ans;
}
下面给出求最小逆元的代码:
typedef long long ll;
ll e_gcd (ll a, ll b, ll& x, ll& y)
{
if (b == 0)
{
x = 1, y = 0;
return a;
}
ll ans = e_gcd (b, a % b, y, x);
y -= a / b * x; //这个和前面用的方法不一样,不过是对的,写起来更快、
return ans;
}
ll Cal(ll a,ll b,ll c)//求最小的x使ax+ by = c
{
ll x,y;
ll gcd = e_gcd(a,b,x,y);
if (c%gcd) return -1;//无解
x*=c/gcd;
b /= gcd;
if (b < 0) b = -b;
ll ans = x%b;
if (ans <= 0) ans += b;
return ans;
}
来一道入门级的裸的扩展欧几里德的题感受下模板怎么用:
PS:方程还是要自己列,然后用扩展欧几里德求解
AC代码供参考:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#define mem(a,x) memset(a,x,sizeof(a))
using namespace std;
typedef long long ll;
ll e_gcd(ll a,ll b,ll &x,ll &y)
{
if (b == 0)
{
x = 1,y = 0;
return a;
}
ll ans = e_gcd(b,a%b,x,y);
ll tmp = x;
x = y;
y = tmp - a/b*y;
return ans;
}
ll cal(ll a,ll b,ll c)//求最小的x使ax+by=c
{
ll x,y;
ll gcd = e_gcd(a,b,x,y);
if (c%gcd != 0) return -1;
x *= c/gcd;
b/=gcd;
if (b < 0) b = -b;
ll ans = x%b;
if (ans <= 0) ans += b;
return ans;
}
int main()
{
ll xa,xb,va,vb,L;
while (~scanf("%lld %lld %lld %lld %lld",&xa,&xb,&va,&vb,&L))
{
ll ans = cal(vb-va,L,xa-xb);
if (ans == -1) puts("Impossible");
else printf("%lld\n",ans);
}
return 0;
}
</