算法競賽入門經典(第2版)—第十章(數論)

零碎知識點
  • 計算最大公約數(輾轉相除法或歐幾里得算法)
int gcd(int a, int b){
	return b==0?a:gcd(b, a%b);
}
  • 計算最小公倍數:lcm(a, b) = a/gcd(a, b) * b。一定寫成先除後乘,如果寫成ab/gcd(a, b),那麼ab可能會溢出。
  • Eratosthenes篩法
int isprime[MAX];
void eratos(int n)//埃拉托色尼篩選法
{
    memset(isprime, 0, sizeof(isprime));
    int m = sqrt(n+0.5);
    for(int i=2; i<=m; i++)
    {
        if(isprime[i]==1) continue;
       //技巧,i*k(k<i)一定已經被標記過了
        for(int j=i*i; j<=n; j+=i)
        {
        	isprime[j] = 1;
        }
    }
}
  • 擴展歐幾里得算法
void exgcd(int a, int b, int &d, int &x, int &y){
	if(!b){
		d = a;
		x = 1;
		y = 0;
	}
	else{
		gcd(b, a%b, d, y, x);
		y -= x*(a/b);
	}
}
int extend_ojld(int a, int b, int &x, int &y)
{
    int d;
    if(b == 0)
    {
        d = a;
        x = 1;
        y = 0;
    }
    else
    {
        d = extend_ojld(b, a%b, y, x);
        y -= (a / b) * x;
    }
    return d;
}
  • int、long long的取值範圍:
題目名稱 題目類型 對應符號
unsigned int 0~4294967295 (10位數,4e9) %u
int -2147483648~2147483647 (10位數,2e9 2^31 - 1) %d
long long -9223372036854775808~9223372036854775807 (19位數, 9e18 ) 2^63 - 1 %lld
unsigned long long 0~18446744073709551615 (20位數,1e19) 2^64 - 1 %llu
  • ^是異或運算符。
  • 唯一分解定理:
    • 先算出範圍內的素數,再分解。
const int maxn=10000+5;
int e[maxn];    //用e[]保存每一位素數的指數
vector<int> primes;//存儲範圍內所有指數
void GetPrime()//獲得範圍的所有的素數
{
	int n[10000]={0};
	for(int i=2;i<=sqrt(10000+0.5);i++)
	{
		if(!n[i])
		{
			for(int j=i*i;j<=10000;j+=i) n[j]=1;
		}
	}
	for(int i=2;i<=10000;i++)
	{
		if(!n[i]) primes.push_back(i);
	}
}
void add_int(int n,int d)
{
	for(int i=0;i<primes.size()&&n!=1;i++)
	{
		while(n%primes[i]==0) {
			n/=primes[i];
			e[i]+=d;
		}
	}
}
  • 直接分解
for(LL i = 2; i <= sqrt(n); i++)
{
    a = 0;
    LL cnt = 0;
    if(n%i == 0)
    {
        primes.push_back(i);
        while(n%i == 0)
        {
            e[cnt] += 1;
            n /= i;
        }
 		cnt++;
    }
}
11582 - Colossal Fibonacci Numbers!

題目鏈接:11582 - Colossal Fibonacci Numbers!

  • 題目大意:輸 入兩個非負整數a、b和正整數n(0<=a,b<=2642^{64},1<=n<=1000),讓你計算f(aba^b)對n取模的值,其中f(0) = 0,f(1) = 1;且對任意非負整數i,f(i+2)= f(i+1)+f(i)。
  • 思路:這道題目既考察了快速冪乘又考察了斐波那契和模的用法。首先需要因爲斐波那契數列值對n取值,可知其序列一定存在一個循環。(因爲f(i+2)= f(i+1)+f(i),當(f[i+1],f[i])二元組重複了就開始了循環,且斐波那契數列取值爲0—n-1範圍內的n個數,所以二元組也有nn個組合,所以nn個值之內必將循環)。
    那麼就先算出循環的長度len,len作爲aba^b中快速冪乘的模,最終計算出f數組的下標,根據下標就可以求出對應的值了。
    數據類型需要使用unsigned long long, 使用scanf("%lld")輸入時測試樣例可以過就是wro,使用cin就過了。後來查到網上說unsigned long long對應的是llu
    代碼:
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;

typedef unsigned long long LL;
const int MAX = 1005;
LL a, b, len, fib[MAX*MAX];
int T, n;

void Fib(int n)
{
    len = 1;
    fib[0] = 0, fib[1] = 1%n;
    for(int i=2; i<=n*n+100; i++)
    {
        fib[i] = (fib[i-1]+fib[i-2])%n;
        if(fib[i]==fib[1] && fib[i-1]==fib[0])
        {
            len = i-1;
            break;
        }
    }
}
//兩種快速冪,第一種更快更好寫
LL Power(LL a, LL b, LL mod)
{
    LL res = 1;
    while(b)
    {
        if(b&1) res = (res*a)%mod;
        a = (a*a)%mod;
        b >>= 1;
    }
    return res;
}
LL Power2(LL a, LL p, LL n) {
	if(p == 0) return 1;
	LL ans = Power2(a, p/2, n);
	ans = ans * ans % n;
	if(p%2 == 1) ans = ans * a % n;
	return ans;
}

int main()
{
#ifdef ONLINE_JUDGE
#else
    freopen("input.txt","r",stdin);
    freopen("output.txt","w",stdout);
#endif
    scanf("%d", &T);
    //cin >> T;
    while(T--)
    {
        //cin >> a >> b >> n;
        scanf("%llu%llu%d", &a, &b, &n);
        Fib(n);
        LL idx = Power(a%len, b, len);
        printf("%d\n", fib[idx]);
    }
    return 0;
}
12169 - Disgruntled Judge

題目鏈接:
參考博文:
12169 - Disgruntled Judge

  • 題目大意:根據xi=(a×xi1+b)mod10001x_i=(a\times x_{i-1}+b)mod10001的計算公式,輸入T和x1,x3...x2T1x_1,x_3...x_{2T-1},然後輸出對應的x2,x4...x2Tx_2,x_4...x_{2T}
  • 思路: 根據x1、x3、a我們可以得出這樣的公式:x3a×a×x1=(a+1)×b+10001×(k)x_3-a\times a\times x_1=(a+1)\times b+10001\times (-k) 。使用擴展歐幾里得算法,我們可以求出b,但是這個過程中a*b有可能會溢出,這題就一定會溢出,所以要用long long,最後得出的b還要乘上c/d 。然後我們就可以使用xi=(a×xi1+b)mod10001x_i=(a\times x_{i-1}+b)mod10001這個公式計算值和判斷值的正確性。

代碼:

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;
LL T,x1[109],x2[109];
void gcd(LL a,LL b,LL& d,LL& x,LL& y) {
    if(!b) { d=a; x=1; y=0; }
    else { gcd(b,a%b,d,y,x); y-=x*(a/b); }
}
void solve() {
    for(int a=10000;a>=0;--a){
        LL d,b,k,c=x1[2]-a*a*x1[1];
        gcd(a+1,10001,d,b,k);
        if(c%d) continue;
        else {
            b=b*c/d;
            int kase=1;
            bool ok=true;
            while(1) {
                x2[kase]=(a*x1[kase]+b)%10001;//計算
                kase++;
                if(kase==T+1) break;
                if(x1[kase]==((a*x2[kase-1]+b)%10001)) continue;//判斷
                else { ok=false; break; }
            } 
            if(ok) return;
        }
    }
}
int main() {
    scanf("%lld",&T);
    for(int i=1;i<=T;i++)
        scanf("%lld",&x1[i]);
    solve();
    for(int i=1;i<=T;i++)
        printf("%lld\n",x2[i]);
    return 0;
}
10791 - Minimum Sum LCM

題目鏈接:10791 - Minimum Sum LCM

  • 題目大意:輸入n,求至少兩個整數,使得他們的最小公倍數爲n,且這些整數的和最小。輸出最小的和。
  • 思路:唯一分解定理。設唯一分解式n=aipi×aip2...n=a_i^{p_i}\times a_i^{p_2}...其中aia_i是素數,不難發現每個aipia_i^{p_i}作爲一個單獨的整數時最優。

代碼:

#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
using namespace std;
typedef long long LL;

int main()
{
    LL n;
    int kase = 0;
    while(scanf("%lld", &n) && n != 0)
    {
        LL a, flag = 0, ans = 0;
        LL x = (long long)sqrt(n) + 10;
        for(LL i = 2; i <= sqrt(n); i++)
        {
            a = 0;
            LL temp = 0;
            if(n %i == 0)//表明該素數可被整除
            {
                flag++;
                temp = 1;
                while(n%i == 0)
                {
                    temp *= i;
                    n /= i;
                }
            }
            ans += temp;
        }
        if(flag == 0)//表明該數是素數或1
            ans = n+1;
        else if(flag == 1 || n != 1)//只有一個因數或n不等於1
            ans += n;
        printf("Case %d: %lld\n",++kase, ans);
    }
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章