寒假集训专题四:数论

ESAY1:有理数取余

【模板】有理数取余

题目描述

给出一个有理数 \(c=\frac{a}{b}\),求 \(c \bmod 19260817\) 的值。

这个值被定义为 \(bx\equiv a\pmod{19260817}\) 的解。

输入格式

一共两行。

第一行,一个整数 \(a\)
第二行,一个整数 \(b\)

输出格式

一个整数,代表求余后的结果。如果无解,输出 Angry!

样例 #1

样例输入 #1

233
666

样例输出 #1

18595654

提示

对于所有数据,保证 \(0\leq a \leq 10^{10001}\)\(1 \leq b \leq 10^{10001}\),且 \(a, b\) 不同时是 \(19260817\) 的倍数。

解题思路:
我们要求c mod p的结果,c=a/b,也就是求a/b mod p的结果。但是这个a/b的值很有可能非常大,我们不能进行直接运算,此时我们需要一个新的数x与a/b mod p同余。
因为如果两个数对模p同余,那么它们乘上同一个数以后依然对模p同余。同乘b,变成bx与a mod p 同余。
bx与a的同余仍然是一个很难算的数,那我们考虑一下如果bx与1同余的情况,是不是会相对好算,再给这个等式乘上一个a,这里的ax就变成原本我们要求的答案,我们把ax里的x设成x1,
bx1与1同余,求解x1。
就是求解b的乘法逆元,利用exgcd(b,p)来求解。(此时有一个结果g=1,即b与p互质导致x1无法求出情况,等会再讨论),求出的结果x可能为负数,将其转化为模p下的正整数。
此时x1已求出,乘上a后再做取余p就能得出答案了。
但是前面也提到了,a和b的输入值可能特别大,那么我们就应该在读入时就直接对p取模,这并不会影响到最后结果。
然后我们来讨论一下什么时候没有解,就是b与p不互质时,无法得到答案。因为我们在输入时就对b进行取模运算了,所以当我们exgcd中读取到b的值是0时,就是b取p的倍数的情况。我们在主函数里直接判断就行了。

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

const int mod = 19260817;
int x, y;

//数据读入,并且如果数据可以进行mod模运算,那就进行,并不影响到最后结果
//因为模运算后的结果与原数同余	 
inline int read() 
{
	int res = 0, ch = getchar();
	while( !isdigit(ch) && ch != EOF )
	{
		ch = getchar();
	}
	while( isdigit(ch) )
	{
		res = ( res << 3 ) + ( res << 1 ) + ( ch - '0' );//res = res * 10 + ch 
		res %= mod;
		ch = getchar();
	}
	return res;
}


//拓展欧几里得算法 
void exgcd( int a, int b )
{
	if( b == 0 )
	{
		x = 1; 
		y = 0;
		return;
	}
	exgcd( b, a % b );
	int temp = x;
	x = y;
	y = temp - a / b * y;
}

int main()
{
	int a,b;
	a = read(); b = read();
	
	if( b == 0 )
	{
		cout << "Angry!" << "\n";
		return 0;
	}
	exgcd( b, mod );
	x = ( x % mod + mod ) % mod;
	cout << a * (long long) (x) % mod << "\n";
	return 0;	
} 

ESAY2:Minimal Coprime

image

解题思路:首先,我们可以想一下两个数互质的条件是什么,gcd(a,b)==1,但是最快的应该是差值为1时,这时候考虑最特殊的情况1,2,在hint里提到[1,2]区间里涵括了[1,1]所以不是最小子区间,说明任何一个数与1组合都不是最小子区间,那么我们就直接把1领出来1单独处理,当左边界为1时,直接将计数结果+1,然后收缩边界,左边界+1,此时我们只要计算多少组数最小互质,也就是在i>1情况下,有多少组[i,i+1],原本区间[l,r]中数的个数有r-l+1,但是因为i+1不能够超过r,所以加上的值应当就是r-l。

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

int main() {
    int t;
    cin >> t;
    while (t--) {
        long long l, r;
        cin >> l >> r;
        long long ans = 0;

        // 特殊情况:如果区间包含1,1一定满足条件
        if (l == 1) {
            ans++;
            l = 2; // 从2开始继续计算
        }

        // 对于每个i > 1,只要i + 1 <= r,就满足条件
        //所以答案就是l到r-1的个数,正常l到r区间一共是r-l+1,所以这里直接r-l 
        ans += max(0LL, r - l);

        cout << ans << "\n";
    }
    return 0;
}

MEDIUM1:素数密度

素数密度

题目背景

UPD:

  • 2024.8.12:加入一组 Hack 数据。

题目描述

给定 \(L,R\),请计算区间 \([L,R]\) 中素数的个数。

\(1\leq L\leq R < 2^{31}\)\(R-L\leq 10^6\)

输入格式

第一行,两个正整数 \(L\)\(R\)

输出格式

一行,一个整数,表示区间中素数的个数。

样例 #1

样例输入 #1

2 11

样例输出 #1

5

解题思路:
我们想到素数以及大数据范围,最好就是使用线性筛这一类的算法,根据数据范围,我们大致确定50000作为数值边界(2的31次方开根号的估算),我们选用vector来作为容器储存素数和是否筛选过的结果。在遍历过程中,如果未被筛选的就推入素数数组,素数与素数的乘积为合数,记录下其一定不为质数,这时候j<prime().size()的判断是必要的防止访问下标越界。访问完确定素数后,我们重新分配vis数组,让他从l开始记录到r的素数里访问哪些素数,从第一素数开始,确认边界从l开始第一个可以被p整除的数开始,至少是pp,从pp和(l+p-1)/p*p中取较大值作为边界,递增p将p的倍数筛去,最后遍历我们使用的访问数组空间,确认其中有多少素数,就可以得到我们的答案。

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

const long long N = 1e6+10;
vector<int> prime;
vector<bool> vis(N, false);

void Gprime( )
{
	for( int i = 2; i <= 50000; i++ )
	{
		if( !vis[i] )
		{
			prime.emplace_back(i);
		}
		for( int j = 0; i * prime[j] <= 50000 && j < prime.size() ; j++ )
		{
			vis[i * prime[j]] = true;
			if( i % prime[j] == 0 )
			{
				break;
			}
		}
	}
}

int main()
{
	long long l, r;
	long long ans = 0;
	ios::sync_with_stdio(false);
	cin >> l >> r;
	l = ( l == 1 ) ? 2 : l;
	Gprime();
	vis.assign( r - l, false );
	for( long long i = 0;i < prime.size(); ++i)//枚举已经筛出来的质数
    {
        long long p = prime[i],start = max( (l + p - 1ll ) / p * p, p * p);
        for(long long j = start; j <= r; j+=p )
        {
        	if( j >= 1 )
        	vis[j-l]=true;
		}
    }
    for( long long i=0; i <= r-l; ++i )
	{
		if(!vis[i])
		{
			ans++;//r-l+1即为区间长度,遍历区间找没有被标记的元素并累加答案
		}
	}
	cout << ans << "\n";
}

MEDIUM2:最大公约数和最小公倍数问题

[NOIP 2001 普及组] 最大公约数和最小公倍数问题

题目描述

输入两个正整数 \(x_0, y_0\),求出满足下列条件的 \(P, Q\) 的个数:

  1. \(P,Q\) 是正整数。

  2. 要求 \(P, Q\)\(x_0\) 为最大公约数,以 \(y_0\) 为最小公倍数。

试求:满足条件的所有可能的 \(P, Q\) 的个数。

输入格式

一行两个正整数 \(x_0, y_0\)

输出格式

一行一个数,表示求出满足条件的 \(P, Q\) 的个数。

样例 #1

样例输入 #1

3 60

样例输出 #1

4

提示

\(P,Q\)\(4\) 种:

  1. \(3, 60\)
  2. \(15, 12\)
  3. \(12, 15\)
  4. \(60, 3\)

对于 \(100\%\) 的数据,\(2 \le x_0, y_0 \le {10}^5\)

【题目来源】

NOIP 2001 普及组第二题

解题思路:
根据最大公约数和最小公倍数的关系可以知道,y0 = P * Q / x0,转化一下就是PQ=x0y0。这样这道题就明朗了,我们只要从1(或者最大公约数)开始遍历到小于等于x*y开根号,记得结果由于对称性,乘以二,在最后考虑一下P,Q相等的特殊情况,进行判断如果相等,答案减一去重。

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

long long gcd( long long a, long long b )
{
	if( b == 0 )
	{
		return a;
	}
	return gcd( b, a % b );
}

int main()
{
	long long x, y;
	cin >> x >> y;
	long long ans = 0;
	bool flag = false;
	for( long long i = x; i * i <= x * y; i++ )
	{
		if( ( x * y ) % i == 0 && gcd( i, x * y / i ) == x )
		{
			ans++;
			if( i * i == x * y )
			{
				flag = true;	
			}	
		}
	}
	cout << ans * 2 + ( ( flag ) ? -1 : 0	 ) << "\n";	
} 

学习总结:

这次数论的内容大多一遍难以理解,多看多动手之后方才明白如何去运用,比如线性筛和欧几里得算法,说明其实很多问题会看重对于题目数学逻辑的理解和剖析。

//欧几里得算法
long long gcd( long long a, long long b )
{
	if( b == 0 )
	{
		return a;
	}
	return gcd( b, a % b );
}
long long gcd( long long a, long long b )
{
	while( b != 0 )
	{
		long long temp = a % b;
		a = b;
		b = temp;
	}
	return a;
}
//拓展欧几里得算法
void exgcd( int a, int b )
{
	if( b == 0 )
	{
		x = 1; 
		y = 0;
		return;
	}
	exgcd( b, a % b );
	int temp = x;
	x = y;
	y = temp - a / b * y;
}
//线性筛的一种
void Gprime( )
{
	for( int i = 2; i <= 50000; i++ )
	{
		if( !vis[i] )
		{
			prime.emplace_back(i);
		}
		for( int j = 0; i * prime[j] <= 50000 && j < prime.size() ; j++ )
		{
			vis[i * prime[j]] = true;
			if( i % prime[j] == 0 )
			{
				break;
			}
		}
	}
}
posted @ 2025-02-10 21:10  yeqa  阅读(43)  评论(0)    收藏  举报