数学渣渣愉快的玩了一把数论,来总结一下几种常用的算法入门,不过鶸也是刚刚入门,
所以也只是粗略的记录下原理,贴下模板,以及入门题目(感受下模板怎么用的)
(PS:文中亮色字体都可以点进去查看百度原文)
附赠数论入门训练专题:点我打开专题(题目顺序基本正常,用以配套数论入门)
简单粗暴的说就是:若 a-b == m 那么 a%m == b%m
这个模运算性质一眼看出。。。直接上入门水题:
Reduced ID Numbers
附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;
x = x0 + (b/gcd)*t; y = y0 - (a/gcd)*t;
倒过去看看欧几里德算法,显然,它的结束条件是 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)
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; }
扩展欧几里德算法有什么用?反正用法很多,它可以求解形如 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 = 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; }
x ≡ a1 (mod m1) x%m1 = a1 x ≡ a2 (mod m2) x%m2 = a2 (S):x ≡ a3 (mod m3) -> x%m3 = a3 ... ... x ≡ an (mod mn) x%mn = an
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 CR(int a[],int m[],int n) { ll M = 1; for (int i = 1;i <= n;++i) M*=m[i]; ll ans = 0; for (int i = 1;i <= n;++i) { ll Mi = M/m[i];ll x,y; ll t = e_gcd(m[i],Mi,x,y); ans = (ans + y*Mi*a[i])%M; } return (M+ans%M)%M; }
#include<iostream> #include<algorithm> #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, y, x); y -= a / b * x; return ans; } ll CR (int a[], int m[], int n) { ll Mi = m[1], ans = a[1]; for (int i = 2; i <= n; ++i) { ll mi = m[i], ai = a[i]; ll x, y; ll gcd = e_gcd (Mi, mi, x, y); ll c = ai - ans; if (c % gcd != 0) return -1; ll M = mi / gcd; ans += Mi * ( ( (c /gcd*x) % M + M) % M); Mi *= M; } if (ans == 0) //当余数都为0 { ans = 1; for (int i = 1; i <= n; ++i) { ans = ans*m[i]/__gcd(ans,(ll)m[i]); } } return ans; } int main() { int T;cin>>T;int kas = 0; while (T--) { int n,a[100],m[100]; scanf("%d",&n); for (int i = 1;i <= n;++i) scanf("%d",m+i); for (int i = 1;i <= n;++i) scanf("%d",a+i); printf("Case %d: %lld\n",++kas,CR(a,m,n)); } return 0; }
#include<iostream> #include<cstdio> #include<cstring> #include<stdlib.h> #include<cmath> #include<algorithm> #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,y,x); y -= a/b*x; return ans; } ll CR(int a[],int m[],int n) { ll M = 1; for (int i = 1;i <= n;++i) M*=m[i]; ll ans = 0; for (int i = 1;i <= n;++i) { ll Mi = M/m[i]; ll x,y; ll t = e_gcd(m[i],Mi,x,y); ans = (ans+y*Mi*a[i])%M; } ans = (M+ans%M)%M; if (ans == 0) //当余数都为0 { ans = 1; for (int i = 1; i <= n; ++i) { ans = ans*m[i]/__gcd(ans,(ll)m[i]); } } return ans; } int main() { int a[4]; int m[4] = {0,23,28,33};int d;int kas = 0; while (~scanf("%d %d %d %d",a+1,a+2,a+3,&d)) { if (a[1]==-1&&a[2]==-1&&a[3]==-1&&d==-1) break; for (int i = 1;i <= 3;++i) { a[i] = a[i] - d; while (a[i]<0) a[i]+=m[i]; } ll ans = CR(a,m,3); printf("Case %d: the next triple peak occurs in %lld days.\n",++kas,ans); } return 0; }
#include<iostream> #include<cstring> #include<cstdio> #define mem(a,x) memset(a,x,sizeof(a)) #define inf (1<<29) using namespace std; typedef long long ll; const int N = 100; bool p[N+5]; void init() { mem(p,1); for (int i = 2;i*i <= N;++i) { if (p[i]) { for (int j = i*i;j <= N;j+=i) { p[j] = 0; } } } } int main() { init(); for (int i = 2;i <= N;++i) { if (p[i]) cout<<i<<endl; } return 0; }素数筛法二:
#include<iostream> #include<cmath> #include<cstdio> #define mem(a,x) memset(a,x,sizeof(a)) #define inf (1<<29) using namespace std; typedef long long ll; const int N = 100; int p[N+5]; void init() { int sq = (int)sqrt(N*2+1); for (int i = 3;i <= sq;i+=2) { if (p[i>>1]) continue; for (int j = i*i;j <= N<<1;j+=i<<1) { p[j>>1] = 1; } } } int main() { init(); puts("2"); for (int i = 1;i < N;++i) { if (p[i]) continue; printf("%d\n",(i<<1)+1); } return 0; }
#include<iostream> #include<cstring> #include<cstdio> #define mem(a,x) memset(a,x,sizeof(a)) #define inf (1<<29) using namespace std; typedef long long ll; const int N = 100000; int a[N+5]; int p[N+5]; void init() { int t = 0; for (int i = 2;i <= N;++i) { if (a[i] == 0) { p[++t] = i; } for (int j = 1,k;(j<=t)&&(k=i*p[j])<=N;++j) { a[k] = 1; if (i%p[j]==0) break; } } } int main() { init(); for (int i = 1;p[i]>1;++i) { printf("%d\n",p[i]); } return 0; }
#include<iostream> #include<cstdio> #include<cstring> #include<string> #include<algorithm> #include<queue> #include<cmath> #include<stdlib.h> #include<cctype> #include<time.h> #define mem(a,x) memset(a,x,sizeof(a)) using namespace std; typedef long long ll; const int S = 8;//测试次数 ll mult_mod (ll a,ll b, ll c) { a%=c,b%=c; ll ret = 0; ll tmp = a; while (b) { if (b&1) { ret += tmp; if (ret > c) ret -= c; } tmp<<=1; if (tmp>c) tmp-=c; b>>=1; } return ret; } ll pow_mod(ll a,ll n,ll mod) { ll ret = 1; ll temp = a%mod; while (n) { if (n&1) ret = mult_mod(ret,temp,mod); temp = mult_mod(temp,temp,mod); n>>=1; } return ret; } bool check(ll a,ll n,ll x,ll t) { ll ret = pow_mod(a,x,n); ll last = ret; for (int i = 1;i <= t;++i) { ret = mult_mod(ret,ret,n); if (ret == 1&&last!=1&&last!=n-1) return 1; last = ret; } if (ret != 1) return 1; else return 0; } bool MR(ll n) { if(n < 2) return 0; if (n == 2) return 1; if ((n&1)==0) return 0; ll x = n - 1; ll t = 0; while ((x&1)==0) { x>>=1;++t; } srand(time(NULL)); for (int i = 0;i < S;++i)//做S次测试 { ll a = rand()%(n-1) + 1; if (check(a,n,x,t)) return 0;//只要其中有一次判定是合数就可以确定一定是合数 } return 1; } int main() { ll n; while (cin>>n) { if (MR(n)) puts("YES"); else puts("NO"); } return 0; }水题: Prime Test
#include<stdio.h> #include<string.h> #include<stdlib.h> #include<time.h> #include<iostream> #include<algorithm> using namespace std; //**************************************************************** // Miller_Rabin 算法进行素数测试 //速度快,而且可以判断 <2^63的数 //**************************************************************** const int S=20;//随机算法判定次数,S越大,判错概率越小 //计算 (a*b)%c. a,b都是long long的数,直接相乘可能溢出的 // a,b,c <2^63 long long mult_mod(long long a,long long b,long long c) { a%=c; b%=c; long long ret=0; while(b) { if(b&1){ret+=a;ret%=c;} a<<=1; if(a>=c)a%=c; b>>=1; } return ret; } //计算 x^n %c long long pow_mod(long long x,long long n,long long mod)//x^n%c { if(n==1)return x%mod; x%=mod; long long tmp=x; long long ret=1; while(n) { if(n&1) ret=mult_mod(ret,tmp,mod); tmp=mult_mod(tmp,tmp,mod); n>>=1; } return ret; } //以a为基,n-1=x*2^t a^(n-1)=1(mod n) 验证n是不是合数 //一定是合数返回true,不一定返回false bool check(long long a,long long n,long long x,long long t) { long long ret=pow_mod(a,x,n); long long last=ret; for(int i=1;i<=t;i++) { ret=mult_mod(ret,ret,n); if(ret==1&&last!=1&&last!=n-1) return true;//合数 last=ret; } if(ret!=1) return true; return false; } // Miller_Rabin()算法素数判定 //是素数返回true.(可能是伪素数,但概率极小) //合数返回false; bool Miller_Rabin(long long n) { if(n<2)return false; if(n==2)return true; if((n&1)==0) return false;//偶数 long long x=n-1; long long t=0; while((x&1)==0){x>>=1;t++;} for(int i=0;i<S;i++) { long long a=rand()%(n-1)+1;//rand()需要stdlib.h头文件 if(check(a,n,x,t)) return false;//合数 } return true; } //************************************************ //pollard_rho 算法进行质因数分解 //************************************************ long long factor[100];//质因数分解结果(刚返回时是无序的) int tol;//质因数的个数。数组小标从0开始 long long gcd(long long a,long long b) { if(a==0)return 1;//??????? if(a<0) return gcd(-a,b); while(b) { long long t=a%b; a=b; b=t; } return a; } long long Pollard_rho(long long x,long long c) { long long i=1,k=2; long long x0=rand()%x; long long y=x0; while(1) { i++; x0=(mult_mod(x0,x0,x)+c)%x; long long d=gcd(y-x0,x); if(d!=1&&d!=x) return d; if(y==x0) return x; if(i==k){y=x0;k+=k;} } } //对n进行素因子分解 void findfac(long long n) { if(Miller_Rabin(n))//素数 { factor[tol++]=n; return; } long long p=n; while(p>=n)p=Pollard_rho(p,rand()%(n-1)+1); findfac(p); findfac(n/p); } int main() { //srand(time(NULL));//需要time.h头文件//POJ上G++不能加这句话 long long n; while(scanf("%I64d",&n)!=EOF) { tol=0; findfac(n); for(int i=0;i<tol;i++)printf("%I64d ",factor[i]); printf("\n"); if(Miller_Rabin(n))printf("Yes\n"); else printf("No\n"); } return 0; }
#include<iostream> #include<cstdio> #include<cstring> #include<string> #include<algorithm> #include<queue> #include<cmath> #include<stdlib.h> #include<cctype> #define mem(a,x) memset(a,x,sizeof(a)) using namespace std; typedef unsigned long long ll; const int S = 20; ll mult_mod (ll a, ll b, ll c) { a %= c, b %= c; ll ret = 0; ll tmp = a; while (b) { if (b & 1) { ret += tmp; if (ret > c) ret -= c; } tmp <<= 1; if (tmp > c) tmp -= c; b >>= 1; } return ret; } ll pow_mod (ll a, ll n, ll mod) { ll ret = 1; ll temp = a % mod; while (n) { if (n & 1) ret = mult_mod (ret, temp, mod); temp = mult_mod (temp, temp, mod); n >>= 1; } return ret; } bool check (ll a, ll n, ll x, ll t) { ll ret = pow_mod (a, x, n); ll last = ret; for (int i = 1; i <= t; ++i) { ret = mult_mod (ret, ret, n); if (ret == 1 && last != 1 && last != n - 1) return 1; last = ret; } if (ret != 1) return 1; else return 0; } bool MR (ll n) { if (n < 2) return 0; if (n == 2) return 1; if ( (n & 1) == 0) return 0; ll x = n - 1; ll t = 0; while ( (x & 1) == 0) { x >>= 1; ++t; } // srand(time(NULL)); for (int i = 0; i < S; ++i) //做S次测试 { ll a = rand() % (n - 1) + 1; if (check (a, n, x, t) ) return 0; //只要其中有一次判定是合数就可以确定一定是合数 } return 1; } ll fac[11111];//质因数分解结果(刚返回时时无序的) int tot;//质因数的个数,数组下标从0开始 ll gcd (ll a, ll b) { if (a == 0) return 1; if (a < 0) return gcd (-a, b); while (b) { ll t = a % b; a = b, b = t; } return a; } ll PR (ll x, ll c) { ll i = 1, k = 2; ll x0 = rand() % x; ll y = x0; while (1) //美丽的循环,不会死 { ++i; x0 = (mult_mod (x0, x0, x) + c) % x; ll d = gcd (y - x0, x); if (d != 1 && d != x) return d; if (y == x0) return x; if (i == k) { y = x0; k += k; } } } void findfac (ll n) //对n进行素因子分解 { if (MR (n) ) //如果n是素数 { fac[tot++] = n; return; } ll p = n; while (p >= n) p = PR (p, rand() % (n - 1) + 1); findfac (p); findfac (n / p); } ll x[111]; ll k; ll a, b, mn; ll ans; ll g, lcm; const ll inf = 1LL << 62LL; bool fd; void dfs (ll cur, ll p) { ll q = ans / p; if (gcd (p, q) == 1) { fd = 1; ll tmp = p * g + q * g; if (mn > tmp) { mn = tmp; a = p*g; b = q*g; } } if (cur > k) return; dfs(cur+1,p);p*=x[cur]; if (p > mn ) return ; dfs(cur+1,p); } int main() { while (~scanf ("%llu %llu", &g, &lcm) ) { if (g == lcm) { printf("%llu %llu\n",g,lcm); continue; } ans = lcm / g; tot = 0; // cout << ans << endl; findfac (ans); sort (fac, fac + tot); //fac保存的是ans的素因子,比如3 60对应的fac数组是2 2 5 // for (int i = 0; i < tot; ++i) cout << fac[i] << " "; // cout << "---------------------------------------------------" << endl; k = 0; x[0] = fac[0]; for (int i = 1; i < tot; ++i) { if (fac[i] == fac[i - 1]) x[k] *= fac[i]; else x[++k] = fac[i]; } sort (x, x + k + 1); //x保存的是所有不相同的素因子,比如3 60 对应的x数组是4 5 // for (int i = 0; i <= k; ++i) cout << x[i] << " "; // cout << endl; //用dfs将数组x分成2部分p*q,满足p,q互质,找到所有p,q中使a+b最小的情况,其中a = p*lcm,b = q*lcm //比如3 60 ans = 20 4 5 ,a = 4*3 b = 5*3 mn = inf; fd = 0; dfs (0, 1); if (a > b) swap (a, b); // if (!fd) puts ("???"); printf ("%llu %llu\n", a, b); } return 0; } /* 7 635040 */
#include<iostream> #include<cmath> #include<algorithm> using namespace std; const int MAXN = 1e5; int isprime[MAXN]; int prime[MAXN]; int cnt; void getP() { cnt = 0; for(int i = 1; i < MAXN; i++) isprime[i] = 1; for(int i = 2; i < MAXN; i++) { if(!isprime[i])continue; prime[cnt++] = i; for(int j = 2 * i; j < MAXN; j += i) { isprime[j] = 0; } } } int euler( int n ) //求小于n且与n互质的数的个数 { int ans = n; for(int i = 0; prime[i] * prime[i] <= n; i++) { if(n%prime[i] == 0) { ans = ans - ans / prime[i]; //ans=ans*(1-1/pi) while(n%prime[i] == 0) { n /= prime[i]; } } } if(n > 1) ans = ans - ans / n; return ans; } int main() { getP(); int n; while(cin >> n&&n) { cout << euler( n ) << endl; } return 0; }