数论从来都是noip的重点内容,我将其做一个小小的总结。一些内容比较基础,请某些大佬勿喷。
- 第一点呢我先不讲数论,当然是墙裂推荐An introduction to theory of numbers,而且一定要买原版的。虽然
有点贵,极其实用!!
快速幂和快速乘
- 当然这个是很多数论实现的基础,在分治算法的时候也应该着重讨论过这种实用算法
- 快速幂:用O(nlogn)的复杂度计算出 a n a ^ n an
- 将指数写成二进制的形式,然后在拆开
- 例如:在计算 5 11 5^{11} 511时可以写成 5 8 ∗ 5 2 ∗ 5 1 {5}^{8}*{5}^{2}*{5}^{1} 58∗52∗51
- 代码:
inline int quickpow(int a, int b, int mod) {
int re = 1;
while(b) {
if(b & 1) re = re * a % mod;
b >>= 1;
a = a * a % mod;
}
return re % mod;
}
- 应该很容易理解。
- 快速乘:防止计算 a ∗ b a * b a∗b溢出
- 和快速乘一样的思想
- 代码:
inline int quickmul(int a, int b, int mod) {
int re = 0;
while(b) {
if(b & 1) re = re + a % mod;
b >>= 1;
a = a + a % mod;
}
return re;
}
- 接下来就是两者结合的快速幂
inline int quickmul(int a, int b, int mod) {
int re = 0;
while(b) {
if(b & 1) re = re + a % mod;
b >>= 1;
a = a + a % mod;
}
return re;
}
inline int quickpow(int a, int b, int mod) {
int re = 1;
while(b) {
if(b & 1) re = quickmul(re, a, mod);
re %= mod;
b >>= 1;
a = quickmul(a, a, mod);
a %= mod;
}
return re;
}
这样理论知识算是结束了,上几道题目,题目太难找了但可以看看luogu的模板题。
整除性和约数
- 如果 a ∣ b , b ∣ c a | b, b | c a∣b,b∣c,则 a ∣ c a | c a∣c(传递性)
- 如果 a ∣ b , c ∣ d a | b, c | d a∣b,c∣d,则 a b ∣ c d ab | cd ab∣cd
- 如果 a ∣ b , a ∣ c a | b,a | c a∣b,a∣c, a ∣ ( n b + m c ) a | (nb + mc) a∣(nb+mc)(线性)
- 如果 m a ∣ m b ma | mb ma∣mb, a ∣ b a | b a∣b
- 这些性质应该是
小学奥数的水平 - 众所周知,素数是大于 1 1 1的整数,且除了它本身以外,没有其他约数。
- 而这里给出素数理论(素数是无穷多的)的证明。
虽然这很显然。 - 用反证法,证明:
- 假设素数有限,设为 p 1 , p 2... p n p1,p2...pn p1,p2...pn其中 p 1 < p 2 < p 3 < . . . < p n p1<p2<p3<...<pn p1<p2<p3<...<pn
- 令 s u m = p 1 ∗ p 2 ∗ . . . ∗ p n + 1 sum=p1*p2*...*pn+1 sum=p1∗p2∗...∗pn+1
- 如果 s u m sum sum是素数,则与已知矛盾
- 如果 s u m sum sum不是素数,但它不能被已知素数整除,与定义矛盾
- 故,素数是无限多的
- 筛素数有两种方法比较优秀 ,一个是埃氏筛法(O(nloglogn)),一个是线性筛法(O(n)),当然线性筛法是一定要掌握的,因为筛欧拉函数的时候肯定要用。
- 埃氏筛法:原名Eratosthenes筛法,是竞赛中常常运用到的,该算法实现简单且时间复杂度及其接近线性。
- 思想:任意整数 x x x的倍数 2 x , 3 x . . . 2x,3x... 2x,3x...,都不是质数。这个很好理解不需要证明。
- 代码:
bool v[N]; //标记是否为素数
inline ll prime(int n) {
memset(v, 0, sizeof(v));
for(int i = 2; i <= n; ++i) {
if(v[i]) continue;//是合数
cout << i << endl;//质数就输出
for(int j = i; j <= n / i; ++j) v[i * j] = 1;//都是合数
}
}
- 在这里有一个技巧,因为如果都从最小的开始, 2 2 2和 3 3 3都会标记 6 6 6,理论上,小于 x 2 x ^ 2 x2的 x x x的倍数在扫描小的数时已经被标记了,只用从 x 2 x^2 x2开始。
- 而线性筛法就是在埃氏筛法基础上只让每个合数被它的最小质因子筛去就可以了。
- 代码:
int v[N], prime[N / 10];
inline ll Prime(int n) {
memset(v, 0, sizeof(v));//最小质因子
m = 0; //个数
for(int i = 2; i <= n; ++i) {
if(!v[i]) {//i是质数
v[i] = i;
prime[++m] = i;
}
for(int j = 1; j <= m; ++j) {
//i有比prime[j]更小的质因子,或者超出n,立刻停止
if(prime[j] > v[i] || prime[j] > n / i) break;
//prime[j]是i*prime[j]的最小质因子
v[i * prime[j]] = prime[j];
}
}
for(int i = 1; i <= m; ++i) cout << prime[i] << endl;
}
- 当然,根据素数定理,素数个数约为 n / 10 n/10 n/10
- 素数定理:设设函数 π ( i ) \pi(i) π(i)表示不超过正实数 的素数个数
- lim x → ∞ \lim_{x \to \infty} limx→∞ π ( x ) x l n x = 1 \frac{\pi(x)}{\frac{x}{lnx}}=1 lnxxπ(x)=1
- 所以 π ( x ) ≈ x l n x \pi(x)\approx\frac{x}{lnx} π(x)≈lnxx
我不会证明,大家可以自行查看复分析理论证明。或者移步素数定理。- 这样我们的第二点也讲完了,来看几道题吧
- UVA10140 Prime Distance
- 洛谷 P1463 [POI2002][HAOI2007]反素数
- 这两道都是比较经典的题目,我会在以后写它们的题解。
我很懒不知道要到什么时候
最大公约数(gcd)和最小公倍数(lcm)
- 当然,这两个东东只是很多书之间的关系
- 如果 a = ∏ p i x i , b = ∏ p i y i a = \prod p_{i}^{x_i},b = \prod p_{i}^{y_{i}} a=∏pixi,b=∏piyi
- g c d ( a , b ) = ∏ p i m i n ( x i , y i ) gcd(a, b)=\prod p_{i}^{min(x_i,y_i)} gcd(a,b)=∏pimin(xi,yi)
- l c m ( a , b ) = ∏ p i m a x ( x i , y i ) lcm(a, b)=\prod p_{i}^{max(x_i,y_i)} lcm(a,b)=∏pimax(xi,yi)
- 很显然 g c d ( a , b ) ∗ l c m ( a , b ) = a ∗ b gcd(a,b)*lcm(a, b)=a*b gcd(a,b)∗lcm(a,b)=a∗b
- 当然 g c d ( a , b ) = 1 gcd(a,b) = 1 gcd(a,b)=1时, a , b a, b a,b互质
- 最大公约数有一个很重要的性质 g c d ( a , b ) = g c d ( b , a % b ) gcd(a, b) = gcd(b,a\%b) gcd(a,b)=gcd(b,a%b)
- 我们叫它欧几里得算法
- 那么
inline int gcd(int a, int b){return !b ? gcd(b, a % b) : a;}
- 代码短小精悍
- 当然还有一种常用的方法,就是九章算术中提到的更相减损法
inline int gcd(int a, int b) {
if(a < b) swap(a, b);
if(a & 1 && !(b & 1)) return gcd(a, b >> 1);
else if(!(a & 1) && b & 1) return gcd(a >> 1, b);
else if(!(a & 1) && !(b & 1)) return gcd(a >> 1, b >> 1) << 1;
else if(a & 1 && b & 1)return gcd(a - b, b);
}
- 如果有人觉得更相减损术没有的话,废话不说上一道题看看
- luoguP2152 [SDOI2009]SuperGCD
- 数据范围为: 10 10000 {10}^{10000} 1010000
- 高精的gcd,如果用高精取模一定很麻烦,所以这题当然是用更相减损术来做。
- 贴一个别人的代码
我太懒了不想敲高精:
#include<cctype>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
struct Big{
int len,d[10001];
void clear(){
len=0;
memset(d,0,sizeof d);
}
void scan(){
char c;
clear();
while(!isdigit(c=getchar()));
d[len=1]=c-'0';
while(isdigit(c=getchar()))d[++len]=c-'0';
reverse(d+1,d+len+1);
}
void print(){
if(len==0)putchar('0');
for(int i=len;i>0;--i)printf("%d",d[i]);
}
bool iseven(){
return !(d[1]&1);
}
void div2(){
for(int i=len;i>0;--i){
if(d[i]&1)d[i-1]+=10;
d[i]>>=1;
}
if(d[len]==0)--len;
}
void mul2(){
for(int i=1;i<=len;++i)d[i]<<=1;
for(int i=1;i<=len;++i){
d[i+1]+=d[i]/10;
d[i]%=10;
}
if(d[len+1])++len;
}
}a,b;
bool operator!= (Big a,Big b){
if(a.len!=b.len)return true;
for(int i=1;i<=a.len;++i)if(a.d[i]!=b.d[i])return true;
return false;
}
bool operator< (Big a,Big b){
if(a.len!=b.len)return a.len<b.len;
for(int i=a.len;i>0;--i)
if(a.d[i]!=b.d[i])return a.d[i]<b.d[i];
return false;
}
void operator-= (Big &a,Big b){
for(int i=1;i<=b.len;++i)a.d[i]-=b.d[i];
for(int i=1;i<=a.len;++i){
if(a.d[i]<0){
a.d[i]+=10;
--a.d[i+1];
}
a.d[i+1]+=a.d[i]/10;
a.d[i]%=10;
}
while(a.len>0&&a.d[a.len]==0)--a.len;
}
int main(){
int tot=0;
a.scan();
b.scan();
while(a!=b){
//a.print();printf(",");b.print();printf("\n=");
if(a<b)swap(a,b);
if(b.len==0)break;
if(a.iseven()&&b.iseven())a.div2(),b.div2(),++tot;
else if(a.iseven())a.div2();
else if(b.iseven())b.div2();
else a-=b;
//a.print();printf(",");b.print();printf("\n");
}
while(tot--)a.mul2();
a.print();
}
- 皮一下:
import fractions
print(fractions.gcd(int(input()),int(input())))
- 这是Python党的福利
丢番图方程
- 裴蜀定理: a x + b y = c ax + by = c ax+by=c有解,当且仅当 g c d ( a , b ) ∣ c gcd(a,b)|c gcd(a,b)∣c
- 扩展gcd(exgcd):求出 a x + b y = c ax + by = c ax+by=c的整数解
- 推理:
- a x 1 + b y 1 = g c d ( a , b ) ax_1 + by_1 = gcd(a,b) ax1+by1=gcd(a,b), b x 2 bx_2 bx2 + ( a % b ) y 2 = g c d ( b , a % b ) (a\%b)y_2 =gcd(b,a\%b) (a%b)y2=gcd(b,a%b)
- 这样我们可以推出
- x 1 = y 2 x_1=y_2 x1=y2, y 1 = x 2 − ( a / b ) y 2 y_1=x_2-(a/b)y_2 y1=x2−(a/b)y2
- 所以我们就有了代码:
inline int exgcd(int a, int b, int &x, int &y) {
if(!b) {
x = 1;
y = 0;
return a;
}
int d = exgcd(b, a % b, x, y);
int z = x;
x = y;
y = z - (a / b) * y;
return d;
}
- 当然,有了一组解我们就可以求出它的通解
- 这里 ( x 1 , y 1 ) (x_1, y_1) (x1,y1)是一组解
- 那么 ( x 1 + k ∗ b g c d ( a , b ) , y 1 − k ∗ a g c d ( a , b ) ) (x_1+\frac{k*b}{gcd(a,b)},y_1-\frac{k*a}{gcd(a,b)}) (x1+gcd(a,b)k∗b,y1−gcd(a,b)k∗a) k ∈ Z k\in Z k∈Z就是通解了
- 这应该很好理解,不需要解释。。。
同余
- 我们称当 m ∣ ( a − b ) m|(a-b) m∣(a−b)成立时, a , b a,b a,b模 m m m同余,记做 a ≡ b ( m o d c ) a\equiv b(mod\ c) a≡b(mod c)
- a ≡ b → b ≡ a a\equiv b\to b\equiv a a≡b→b≡a
- a ≡ b , b ≡ c → a ≡ c a\equiv b,b\equiv c\to a\equiv c a≡b,b≡c→a≡c
- a ≡ a ′ , b ≡ b ′ , . . . → P ( a , b , . . . ) ≡ P ( a ′ , b ′ , . . . ) a\equiv {a}',b\equiv b',...\to P(a,b,...)\equiv P(a',b',...) a≡a′,b≡b′,...→P(a,b,...)≡P(a′,b′,...)
- 剩余系这个作为证明欧拉函数的积性的前置芝士
- 当然同余运算满足 a ≡ a ′ ( m o d m ) → k a ≡ k a ′ ( m o d m ) a\equiv a'(mod\ m)\to ka\equiv ka'(mod\ m) a≡a′(mod m)→ka≡ka′(mod m)
- 但只支持正向,不支持反向
欧拉函数
- 积性函数:若对于任意 g c d ( a , b ) gcd(a, b) gcd(a,b)都有 f ( m n ) = f ( m ) f ( n ) f(mn)=f(m)f(n) f(mn)=f(m)f(n)则我们称该函数为积性函数
- 欧拉函数:表示 [ 1 , n ] [1,n] [1,n]中与 n n n互质的正整数的个数
- 如 ϕ ( 8 ) = 4 \phi(8)=4 ϕ(8)=4, ϕ ( 9 ) = 6 \phi(9)=6 ϕ(9)=6
- ϕ ( p a ) = p a − p a − 1 \phi(p^a)=p^a-p^{a-1} ϕ(pa)=pa−pa−1(p为质数)
- 下面给出欧拉函数为积性函数的证明
- g c d ( a ′ m + a m ′ , m m ′ ) = 1 ⇔ g c d ( a ′ m + a m ′ , m ) = 1 且 g c d ( a ′ m + a m ′ , m ′ ) = 1 ⇔ g c d ( a m ′ , m ) = 1 且 g c d ( a ′ m , m ′ ) = 1 ⇔ g c d ( a , m ) = 1 且 g c d ( a ′ , m ) = 1 gcd(a'm+am',mm')=1\Leftrightarrow gcd(a'm+am',m) = 1且gcd(a'm+am',m')=1\\ \Leftrightarrow gcd(am',m)=1 且gcd(a'm,m')=1 \Leftrightarrow gcd(a,m)=1且gcd(a',m)=1 gcd(a′m+am′,mm′)=1⇔gcd(a′m+am′,m)=1且gcd(a′m+am′,m′)=1⇔gcd(am′,m)=1且gcd(a′m,m′)=1⇔gcd(a,m)=1且gcd(a′,m)=1
- 这样再结合剩余系的定义就很容易看出欧拉函数积性了
- 我们用线性筛可以筛出积性函数
(1):对于素数 p p p, ϕ ( p ) = p − 1 \phi(p)=p-1 ϕ(p)=p−1
(2):若 i % p r i m e [ j ] ! = 0 i\%prime[j]!=0 i%prime[j]!=0则 ϕ ( i ∗ p r i m e [ j ] ) = ϕ ( i ) ∗ ϕ ( p r i m e [ j ] ) = ϕ ( i ) ∗ ( p r i m e [ j ] − 1 ) \phi(i*prime[j])=\phi(i)*\phi(prime[j])=\phi(i)*(prime[j]-1) ϕ(i∗prime[j])=ϕ(i)∗ϕ(prime[j])=ϕ(i)∗(prime[j]−1)
(3):若 i % p r i m e [ j ] = 0 i\%prime[j]=0 i%prime[j]=0,则用定义式求出 ϕ ( i ∗ p r i m e [ j ] ) \phi(i*prime[j]) ϕ(i∗prime[j])与 ϕ ( i ) \phi(i) ϕ(i)的关系 - 代码:
inline void euler(int n) {
memset(v, 0, sizeof(v));
m = 0;
for(int i = 2; i <= n; ++i) {
if(!v[i]) {
v[i] = i;
prime[++m] = i;
phi[i] = i - 1;
}
for(int j = 1; j <= m; ++j) {
if(prime[j] > v[i] || prime[j] > n / i) break;
v[i * prime[j]] = prime[j];
phi[i * prime[j]] = phi[i] * (i % prime[j] ? prime[j] - 1 : prime[j]);
}
}
}
- 【bzoj2190】[SDOI2008]仪仗队:
题面:
作为体育委员,C君负责这次运动会仪仗队的训练。
仪仗队是由学生组成的N * N(N<=40000)的方阵,为了保证队伍在行进中整齐划一,C君会跟在仪仗队的左后方,根据其视线所及的学生人数来判断队伍是否整齐(如下图)。
现在,C君希望你告诉他队伍整齐时能看到的学生人数。