寒假集训专题四:数论
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

解题思路:首先,我们可以想一下两个数互质的条件是什么,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\) 的个数:
-
\(P,Q\) 是正整数。
-
要求 \(P, Q\) 以 \(x_0\) 为最大公约数,以 \(y_0\) 为最小公倍数。
试求:满足条件的所有可能的 \(P, Q\) 的个数。
输入格式
一行两个正整数 \(x_0, y_0\)。
输出格式
一行一个数,表示求出满足条件的 \(P, Q\) 的个数。
样例 #1
样例输入 #1
3 60
样例输出 #1
4
提示
\(P,Q\) 有 \(4\) 种:
- \(3, 60\)。
- \(15, 12\)。
- \(12, 15\)。
- \(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;
}
}
}
}
浙公网安备 33010602011771号