NOIP數學複習
NOIP中的數學相關知識
素數及其相關
a.判斷素數 複雜度O(根號n)
bool prime(int x) {
if(x == 0 || x == 1) return false;
for(int i = 2; i * i <= x; i++) {
if(x % i == 0) return false;
}
return true;
b.篩素數
埃氏篩法 複雜度O(n loglog n)
對於每個數p,會劃掉p/n個數
#include<iostream>
#include<cstdio>
using namespace std;
const int SIZE=1e7;
int prime[SIZE];// 第i個素數
bool is_prime[SIZE];//true表示i是素數
int slove(int n)
{
int p = 0;
for(int i = 0; i <= n; i++)
is_prime[i] = true;//初始化
is_prime[0] = is_prime[1] = false;//0,1不是素數
for(int i = 2; i <= n; i++)
{
if(is_prime[i])//zkzk
{
prime[p++] = i;//計算素數的個數,也記錄下了素數
for(int j = 2 * i; j <= n; j += i)// 除掉了i的倍數的數字
is_prime[j] = false;
}
}
return p;
}
int main()
{
int n;
while(cin >> n)
{
int res = slove(n);
cout << res << endl;//素數個數
for(int i = 0; i < res; i++)//列出素數
cout << prime[i] << endl;
}
}
//zk:初始最小素數是2,將資料內2的所有倍數扔掉。此時3不能被更小的數整出,即為素數依次類推;
尤拉篩法 複雜度O(n)
luogu 線篩模板:
#include <cstdio>
using namespace std;
const int maxn=10000000;
int n,m;
int prime[maxn],flag[maxn];
int tot;
void get_prime(int n){
flag[0]=flag[1]=1;//不是素數
for (int i=2; i<=n; ++i)
{
if (!flag[i]) prime[++tot]=i;
for (int j=1; j<=tot&&i*prime[j]<=n; ++j)
{
flag[i*prime[j]]=1;
if (i%prime[j]==0) break;//prime[j]必定是prime[j]*i的最小因子;i中有因子prime[j],已被篩到一次 就退出找下一個i
}
}
}
int main(){
scanf("%d%d", &n, &m);
get_prime(n);
int t;
for (int i=1; i<=m; ++i)
{
scanf("%d", &t);
if (!flag[t]) printf("Yes\n");
else printf("No\n");
}
return 0;
}
2.區間篩素數
(poj2689)
篩[a,b)中的素數
因為b以內合數的最小質因數一定不超過sqrt(b),先分別做好[2,sqrt(b))的表和[a,b)的表,然後從[2,sqrt(b))的表中篩得素數的同時,也將其倍數從[a,b)的表中劃去,最後剩下的就是區間[a,b)內的素數。
用埃氏篩的原理:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
const ll maxn=1e6+10;
bool is_prime[maxn],is_p[maxn];//is_prime[]表示le~ri中下邊位移後的素數判定(a位移1),is_p[]表示1~根號n中的素數判定
ll prime[maxn],sum;
void get_prime(ll le,ll ri){
for(ll i=2;i*i<ri;i++) is_p[i]=true;//對[2,sqrt(b))的初始化全為質數,i要從2開始, 1不是素數
for(ll i=1;i<=ri-le;i++) is_prime[i]=true;//對下標偏移後的[a,b)進行初始化,i要從1開始
for(ll i=2;i*i<=ri;i++){
if(is_p[i])for(ll j=2*i;j*j<=ri;j+=i) is_p[j]=false;//篩選素數i的倍數
for(ll j=max(2LL,(le+i-1)/i) * i;j<=ri;j+=i) is_prime[j-le]=false;//(a+i-1)/i得到最接近a的i的倍數,最低是i的2倍,然後篩選
}
for(ll i=1;i<ri-le;i++) if(is_prime[i]) prime[++sum]=i+le;
}
int main(){
ll l,r;
while(scanf("%lld%lld",&l,&r)){
sum=0; memset(prime,0,sizeof(prime));
get_prime(l,r);
printf("%lld\n",sum);
}
return 0;
}
3.分解質因數
基於唯一分解定理
n = P1^a1 * P2^a2 * …………* Pn^an(P1 < P2 < ……Pn),Pi為質數;
樸素法(可以先求出素數來優化一下)
void fj(int x) {
for(int i = 2; i * i <= x && x > 1; i++) {
while(x % i == 0) {
if(!check[i]) zhi[++cnt] = i;//check判斷有沒有出現過
check[i]++;
x /= i;
if(x == 1) break;
}
}
}
快速冪
// a ^ b % mod
int ksm(int a,int b,int mod) {
int ans=1;
a%=mod
while(b){
if(b&1)ans=(ans*a)%mod;
b>>=1;
a=(a*a)%mod
}
return ans;
}
排列組合
組合
void init(){
f[0][0]=1;
for(int i = 1; i <= inf; ++i){
f[i][0]=1;
for(int j = 1;j <= i; ++j){
f[i][j] = f[i-1][j] + f[i-1][j-1];
}
}
}
next_permutation(a +1,a+1+n); STL全排列
同餘
同餘的性質
0.0
歐幾里得演算法
好久之前的筆記——關於gcd,exgcd,cal
/*設兩數為a、b(a>b),求a和b最大公約數(a,b)的步驟如下:
用a除以b,得a÷b=q……r1(0≤r1)。
若r1=0,則(a,b)=b;若r1≠0,則再用b除以r1,得b÷r1=q……r2 (0≤r2).
若r2=0,則(a,b)=r1,若r2≠0,則繼續用r1除以r2,……
如此下去,直到能整除為止。其最後一個非零除數即為(a,b)。*/
//—————————————————————————————————-
#include<stdio.h>
#define ll long long
ll gcd(ll a,ll b){
return b==0?a:gcd(b,a%b);
}
int main(){
ll a,b;
while(scanf("%lld%lld",&a,&b)!=EOF)
{
printf("%lld\n",gcd(a,b));//事實上如果 a 小於 b,那第一次就會先交換 a 與 b。
}
return 0;
}
//--------------------------------------------------------
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);}
}
//逆元
ll cal(ll a,ll m)
{
ll d,x,y;
exgcd(a,m,d,x,y);
return (x%m+m)%m;
}
int main(){
ll a,b,d,x,y;
while(scanf("%lld%lld",&a,&b)!=EOF){
exgcd(a,b,d,x,y);
printf("%lld*%lld+%lld*%lld=%lld\n",a,x,b,y,d);
}
return 0;
}
/*
同餘方程
先講一下擴充套件歐幾里德定律:
對於不完全為0的非負整數a,b,gcd(a, b)表示a, b的最大公約數,必定存在整數對x,y,滿足a*x+b*y==gcd(a, b)。
證明:(轉)
a*x1+b*y1=gcd(a, b);
b*x2+(a%b)*y2=gcd(b, a%b);
因為由歐幾里德定理知:gcd(a, b)==gcd(b, a%b)
所以a*x1+b*y1=b*x2+(a%b)*y2; 因為r=a%b, r =a-k*b所以==>
a*x1+b*y1=b*x2+(a-k*b)*y2; 因為k=a/b;所以==>
a*x1+b*y1=b*x2+(a-(a/b)*b)*y2; 展開得到==>
a*x1+b*y1=b*x2+a*y2-b*(a/b)*y2;轉換得到 ==>
a*x1+b*y1=a*y2+b*(x2-(a/b)*y2);
觀察上式可知 x1=y2, y1=x2-a/b*y2;
由此可知x1,y1是由x2,y2得出來的,由此類推x2,y2是由x3,y3得出來的,
那什麼時候是終止呢?也就是遞迴gcd(a, b)中b=0時;也就是說此時a的值就是要求得最大公約數
即gcd(a, 0)此時由擴充套件歐幾里得定律a*x+b*y==gcd(a, b)
知 a*x+b*y=a;
解出x=1, y=0;
此時就是遞迴終止的地方:
——————–分界線————————
那麼問題來了,這破東西有啥用呢
問的好,它可以用來求一個同餘方程的解,也就是逆元
ax ≡ 1 (mod b),現在找x能夠使這個式子成立
這個式子等價於ax+by=1
這不就是拓展歐幾里得的表示式嗎,從這裡我們可以看出如果gcd(a,b)!=1那麼這個方程就不會有解
所以說呢
形如a*x + b*y = c這樣的式子,若c%gcd(a,b)==1,那麼這個方程就會有解
PS:題目保證有解,所以不用判斷了
但是一般題目裡會讓你求一個最小的x,當你用拓歐求出一個解時,一般會讓你去找一個最小解,我們只需要對這個數取模b就行了(如果求正數,你只需要先加一個b,再取模行了,應該都知道吧)
程式碼如下
#include <cstdio>
using namespace std;
typedef ll ll;
ll a,b,x,y;
ll e_gcd(ll a,ll b,ll &x,ll &y)
{
if(!b)
{
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;
}
int main()
{
scanf("%lld%lld",&a,&b);
e_gcd(a,b,x,y);
printf("%lld",(x+b)%b);
return 0;
}
1.最大公因數
int gcd(int a,int b) {
if(!b) return a;
else return gcd(b, a % b);
}
2.最小公倍數
gcd(a,b) * lcm(a,b) = a * b;
lcm(a,b) = a * b / gcd(a,b);
int lcm(int a,int b) {
return a * b / gcd(a,b);
}
霍dalao的醜字:
擴充套件歐幾里得演算法
void exgcd(int a, int b, int &d, int &x, int &y) {
if(!b) {d = a, x = 1, y = 0, return;}
exgcd(b, a % b, d , y, x);
y -= x * (a / b);
}
擴歐求解不定方程:
逆元
1.擴歐
int inv(int a, int n) {
int d,x,y;
exgcd(a,n,d,x,y);
return (x % n + n) % n;
}
2.費馬小定理
a^(p-1) ≡1 (mod p)
int inv(int a, int n) {
return ksm(a,n-2,n)
}
遞推規律
1.Catalan數
h(n)=(4n-2)/(n+1)*h(n-1)(n>1) h(0)=1
h(n)= h(0)*h(n-1)+h(1)*h(n-2) + … + h(n-1)h(0) (n>=2)
h(n)=C(2n,n)/(n+1)
2.錯排公式
十本不同的書放在書架上。現重新擺放,使每本書都不在原來放的位置。有幾種擺法?
f[1] = 0, f[2] = 1
f[i]=(i-1)*(f[n-1]+f[n-2])
3.斐波那契數列
f[1] = 1, f[2] = 1;
f[i] = f[i - 1] + f[i - 2]
5.除數函式
設 d(n)為 n 的所有因數的個數,由乘法原理可知,
d(n)=(a1 +1)(a2 +1)…(ak +1)。
4.秦九韶演算法
int qing(int x){
int ans=a[n];
for(int i=n-1;i>=1;i--)
ans=ans*x+a[i];
return ans;
}