1. 程式人生 > >ACM基礎數論

ACM基礎數論

數論這塊東西很雜,一個一個定理學,基本就是數學知識。

所以說這種題數學知識比程式碼更重要,主要就是學一些解題必備的數學定理。我用了好久終於把基礎數論裡面的一個一個定理全磨會了,不過不完整,現在光寫一些我學過的比較基礎的數論知識,後面慢慢補充。

拓展歐幾里德演算法

首先是歐幾里得演算法求最大公約數。這個就是輾轉相除法,應該都會。

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

數論的題比較容易涉及大數,所以一般開變數用的都是long long型,程式碼開頭#define ll long long用ll代替一下。再大的數就會用字元陣列進行記錄,文章後面會提到。

這個太簡單了一般不會這麼考,我們要學習的就是拓展歐幾里德演算法。對於ax+by=c的解的問題,當且僅當c%gcd(a,b)!=0時有解。用遞迴的方法求解,證明略。(證明真的很複雜)直接上例題和板子。拓展歐幾里德函式就是板子裡的ggcd函式。

codeforce7C題

A line on the plane is described by an equation Ax + By + C = 0. You are to find any point on this line, whose coordinates are integer numbers from  - 5·1018

 to 5·1018inclusive, or to find out that such points do not exist.

The first line contains three integers AB and C ( - 2·109 ≤ A, B, C ≤ 2·109) — corresponding coefficients of the line equation. It is guaranteed that A2 + B2 > 0.

If the required point exists, output its coordinates, otherwise output 

-1.

題意,求Ax+By+C=0的解,給你ABC,輸出xy,不存在則輸出-1.

#include <iostream>
using namespace std;
#define ll long long 
ll x,y;
ll gcd(ll a,ll b)
{
	return b==0?a:gcd(b,a%b);
}
void ggcd(ll a,ll b)
{
    ll t;
    if (b==0)
    {
        x=1;
        y=0;
        return;
    }
    else
    {
        ggcd(b,a%b);
        t=x;
        x=y;
        y=t-(a/b)*y;
    }
}
int main()
{
    int T;
	ll n,b,c;
    cin>>b>>c>>n;
    ll g=gcd(b,c);
	if(n%g!=0)
	{
		cout<<-1<<endl;
		return 0;
	}
	b/=g;c/=g;n/=g;
	ggcd(b,c);
	x*=-n;y*=-n;//Ax+By=-C
    cout<<x<<" "<<y<<endl;
    return 0;
}

由題可得公式為Ax+By=-C,套用歐幾里德演算法,注意用之前先/=g(倒數第五行)

哇,瞬間會了一道CF的C題,好開心。

例題2 HDU1576

要求(A/B)%9973,但由於A很大,我們只給出n(n=A%9973)(我們給定的A必能被B整除,且gcd(B,9973) = 1)。

由題得A=n+y*9973=x*B,x為所求,y為任意值,由於題目給出gcd(B,9973)=1,所以一定有解,直接上ggcd(B,9973),輸出x即可。上程式碼。
#include <iostream>  
using namespace std;  
int x,y;  
void gcd(int a,int b)  
{  
    int t;  
    if (b==0)  
    {  
        x=1;  
        y=0;  
        return;                                   
    }  
    else  
    {  
        gcd(b,a%b);                              
        t=x;  
        x=y;  
        y=t-(a/b)*y;  
    }  
  
}  
int main()  
{  
    int T,n,b;  
    cin>>T;  
    while (T--)  
    {  
        cin>>n>>b;  
        gcd(b,9973);  
        if (x<0)  
            x+=9973;
        x*=n;                               
        cout<<x%9973<<endl;  
    }  
    return 0;  
} 
矩陣快速冪

先看快速冪。用cmath裡的pow函式計算次方運算太慢,而且不能在計算中取模。於是,快速冪誕生了!

對於a^b,如果b是偶數,ans=a*a,b/2,如果b是奇數,ans=a*a*ans

long long pow_mod(long long a,long long n)
{
	long long res=1;
	while(n>0)
	{
		if(n%2==1)//if(n&1)
		res=res*a%mod;
		a=a*a%mod;
		n/=2;//n=n>>1;
	}
	return res;
}

矩陣快速冪則定義一個矩陣乘法,再進行快速冪運算。

如HDU1005

A number sequence is defined as follows: 

f(1) = 1, f(2) = 1, f(n) = (A * f(n - 1) + B * f(n - 2)) mod 7. 

Given A, B, and n, you are to calculate the value of f(n). 

由線性代數知識得

設一個a  2,2的矩陣

a[0][0]=A a[0][1]=B a[1][0]=1 a[1][1]=0

f[n]=a^n-2[0][0]+a^n-2[0][1]

所以先將矩陣乘n次方,再取矩陣前兩個元素相加得f[n]

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MOD=7;
typedef vector<LL>vec;
typedef vector<vec>mat;
LL n;
mat mul(mat &A,mat &B)
{
    mat C(A.size(),vec(B[0].size()));
    for( int i=0;i<A.size();i++){
        for( int j=0;j<B[0].size();j++ ){
            for( int k=0;k<B.size();k++ ){
                C[i][j]=(C[i][j]+A[i][k]*B[k][j]);
                C[i][j]%=MOD;
            }
        }
    }
    return C;
}
mat pow(mat A,LL n)
{
    mat B(A.size(),vec(A.size()));
    for( int i=0;i<A.size();i++){
        B[i][i]=1;
    }
    while(n>0){
        if(n&1)B=mul(B,A);
        A=mul(A,A);
        n>>=1;
    }
    return B;
}
void solve(LL a,LL b)
{
    mat A(2,vec(2));
    A[0][0]=a;
    A[0][1]=b;
    A[1][0]=1;
    A[1][1]=0;
    A=pow(A,n-2);
    printf("%lld\n",(A[0][0]+A[0][1])%7);
}
int main()
{
    LL a,b;
    while(~scanf("%lld%lld%lld",&a,&b,&n),a)
        solve(a,b);
    return 0;
}