算法竞赛入门经典(第2版)—第十章(数论)

文章目录

          • 零碎知识点
          • 11582 - Colossal Fibonacci Numbers!
          • 12169 - Disgruntled Judge
          • 10791 - Minimum Sum LCM

零碎知识点
  • 计算最大公约数(辗转相除法或欧几里得算法)
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
        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<= 2 64 2^{64} 264,1<=n<=1000),让你计算f( a b a^b ab)对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作为 a b a^b ab中快速幂乘的模,最终计算出f数组的下标,根据下标就可以求出对应的值了。
    数据类型需要使用unsigned long long, 使用scanf("%lld")输入时测试样例可以过就是wro,使用cin就过了。后来查到网上说unsigned long long对应的是llu
    代码:
#include 
#include 
#include 
#include 
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

  • 题目大意:根据 x i = ( a × x i − 1 + b ) m o d 10001 x_i=(a\times x_{i-1}+b)mod10001 xi=(a×xi1+b)mod10001的计算公式,输入T和 x 1 , x 3 . . . x 2 T − 1 x_1,x_3...x_{2T-1} x1,x3...x2T1,然后输出对应的 x 2 , x 4 . . . x 2 T x_2,x_4...x_{2T} x2,x4...x2T
  • 思路: 根据x1、x3、a我们可以得出这样的公式: x 3 − a × a × x 1 = ( a + 1 ) × b + 10001 × ( − k ) x_3-a\times a\times x_1=(a+1)\times b+10001\times (-k) x3a×a×x1=(a+1)×b+10001×(k) 。使用扩展欧几里得算法,我们可以求出b,但是这个过程中a*b有可能会溢出,这题就一定会溢出,所以要用long long,最后得出的b还要乘上c/d 。然后我们就可以使用 x i = ( a × x i − 1 + b ) m o d 10001 x_i=(a\times x_{i-1}+b)mod10001 xi=(a×xi1+b)mod10001这个公式计算值和判断值的正确性。

代码:

#include
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 = a i p i × a i p 2 . . . n=a_i^{p_i}\times a_i^{p_2}... n=aipi×aip2...其中 a i a_i ai是素数,不难发现每个 a i p i a_i^{p_i} aipi作为一个单独的整数时最优。

代码:

#include
#include
#include
#include
#include
#include
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;
}

你可能感兴趣的:(算法竞赛入门经典(第2版))