GESP C++5级 2025年6月编程2题解:最大公因数 - 教程

P13014 [GESP202506 五级] 最大公因数

题目描述

对于两个正整数 a,ba,ba,b,他们的最大公因数记为 gcd⁡(a,b)\gcd(a,b)gcd(a,b)。对于 k>3k > 3k>3 个正整数 c1,c2,…,ckc_1,c_2,\dots,c_kc1,c2,,ck,他们的最大公因数为:

gcd⁡(c1,c2,…,ck)=gcd⁡(gcd⁡(c1,c2,…,ck−1),ck)\gcd(c_1,c_2,\dots,c_k)=\gcd(\gcd(c_1,c_2,\dots,c_{k-1}),c_k)gcd(c1,c2,,ck)=gcd(gcd(c1,c2,,ck1),ck)

给定 nnn 个正整数 a1,a2,…,ana_1,a_2,\dots,a_na1,a2,,an 以及 qqq 组询问。对于第 i(1≤i≤q)i(1 \le i \le q)i(1iq) 组询问,请求出 a1+i,a2+i,…,an+ia_1+i,a_2+i,\dots,a_n+ia1+i,a2+i,,an+i 的最大公因数,也即 gcd⁡(a1+i,a2+i,…,an+i)\gcd(a_1+i,a_2+i,\dots,a_n+i)gcd(a1+i,a2+i,,an+i)

输入格式

第一行,两个正整数 n,qn,qn,q,分别表示给定正整数的数量,以及询问组数。

第二行,nnn 个正整数 a1,a2,…,ana_1,a_2,\dots,a_na1,a2,,an

输出格式

输出共 qqq 行,第 iii 行包含一个正整数,表示 a1+i,a2+i,…,an+ia_1+i,a_2+i,\dots,a_n+ia1+i,a2+i,,an+i 的最大公因数。

输入输出样例 #1

输入 #1

5 3
6 9 12 18 30

输出 #1

1
1
3

输入输出样例 #2

输入 #2

3 5
31 47 59

输出 #2

4
1
2
1
4

说明/提示

对于 60%60\%60% 的测试点,保证 1≤n≤1031 \le n \le 10^31n1031≤q≤101 \le q \le 101q10

对于所有测试点,保证 1≤n≤1051 \le n \le 10^51n1051≤q≤1051 \le q \le 10^51q1051≤ai≤10001 \le a_i \le 10001ai1000

题目大意

给定nnn个正整数组成的序列aaaqqq次询问,对于第i(1≤i≤q)i(1\le i\le q)i(1iq)次询问,输出gcd⁡(a1+i,a2+i,…,an+i)\gcd(a_1+i,a_2+i,\dots,a_n+i)gcd(a1+i,a2+i,,an+i)的值。

题目分析

先说TLE的解法。
最简单的办法就是在输入后直接一次一次反复求整个数组加iii的最大公因数,意思就是直接处理每次询问,每次询问都遍历数组且求一遍gcd⁡\gcdgcd,这是很多考生都用的办法。

欧几里得算法模板

欧几里得算法(辗转相除法)求最大公因数可以用下面的精简代码

int gcd(int x,int y)
{
return y==0?x:gcd(y,x%y);
}

也可以使用迭代

int gcd(int x,int y)
{
while(y!=0)
{
int tmp=y;
y=x%y;
x=tmp;
}
return x;
}

复杂度如下表

递归迭代
时间复杂度O(log⁡n)O(\log n)O(logn)O(log⁡n)O(\log n)O(logn)
空间复杂度O(log⁡n)O(\log n)O(logn)O(1)O(1)O(1)

递归的空间用于递归栈调用,该题中无需考虑。

TLE Code

#include<bits/stdc++.h>
  using namespace std;
  int a[100001];
  int gcd(int x,int y)
  {
  return y==0?x:gcd(y,x%y);
  }
  int main()
  {
  int n,q;
  cin>>n>>q;
  for(int i=1;i<=n;i++)cin>>a[i];
    for(int i=1;i<=q;i++)
    {
    int g=a[1]+i;
    for(int j=2;j<=n;j++)g=gcd(g,a[j]+i);
    cout<<g<<endl;
    }
    return 0;
    }

在这里插入图片描述
但是一旦提交
在这里插入图片描述
就噶了。

TLE Time

输入比处理快,只看处理部分。
外重循环:O(q)O(q)O(q)
内重循环:O(n)O(n)O(n)
gcd⁡\gcdgcdO(log⁡ai)O(\log a_i)O(logai)
总时间:O(nqlog⁡ai)O(nq\log a_i)O(nqlogai),数据范围不让过。
那过的方法是什么呢?

差分数组

对于长度为nnn的序列aaa,他的差分数组第一项与原数组第一项相等,后面差分数组第i(2≤i≤n)i(2\le i\le n)i(2in)项等于ai−ai−1a_i-a_{i-1}aiai1,注意即使是负数也不能绝对值。
现在,我要证明一个东西:gcd⁡(a+c,b+c)=gcd⁡(a+c,b−a)\gcd(a+c,b+c)=\gcd(a+c,b-a)gcd(a+c,b+c)=gcd(a+c,ba)

证明gcd⁡(a+c,b+c)=gcd⁡(a+c,b−a)\gcd(a+c,b+c)=\gcd(a+c,b-a)gcd(a+c,b+c)=gcd(a+c,ba)

众所周知,辗转相除法一开始是辗转相减法,后来通过优化才变成辗转相除法。
后面的取模比之前的相减快多了,但是不要忘本。
对于gcd(a,b),可以推出gcd(b,a-b),他们是等价的,你可以想象成在一个大长方形里面取截取最大的正方形,剩下的长宽就是gcd的参数。
那么用于gcd⁡(a+c,b+c)=gcd⁡(a+c,b−a)\gcd(a+c,b+c)=\gcd(a+c,b-a)gcd(a+c,b+c)=gcd(a+c,ba)也好使,就假设a+ca+ca+ca′a'ab+cb+cb+cb′b'b
gcd⁡(a′,b′)=gcd⁡(a′,b−a)\gcd(a',b')=\gcd(a',b-a)gcd(a,b)=gcd(a,ba)。你感觉不一样。
来,别急,aaabbb的大小关系是要调整的,gcd⁡(a′,b−a)\gcd(a',b-a)gcd(a,ba)变成gcd⁡(b′,a−b)\gcd(b',a-b)gcd(b,ab),是不是恍然大悟了?
没错,跟辗转相减法几乎一样了。其实这个式子是gcd⁡(a,b)\gcd(a,b)gcd(a,b)演变而来,他就是把a,ba,ba,b都加上ccc,证明新aaaa,ba,ba,b差的最大公因数就是新的两个数的最大公因数。
这样,就能在一串数中,整体递增相同元素后快速找到最大公因数。
没听懂证明过程?举个栗子!

举个栗子

例如3 7c=1c=1c=1gcd⁡(a+c,b+c)=gcd⁡(4,8)=4\gcd(a+c,b+c)=\gcd(4,8)=4gcd(a+c,b+c)=gcd(4,8)=4gcd⁡(a+c,b−a)=gcd⁡(4,4)=4\gcd(a+c,b-a)=\gcd(4,4)=4gcd(a+c,ba)=gcd(4,4)=4,确实相等。
再比如5 6c=2c=2c=2gcd⁡(a+c,b+c)=gcd⁡(7,8)=1\gcd(a+c,b+c)=\gcd(7,8)=1gcd(a+c,b+c)=gcd(7,8)=1gcd⁡(a+c,b−a)=gcd⁡(7,1)=1\gcd(a+c,b-a)=\gcd(7,1)=1gcd(a+c,ba)=gcd(7,1)=1,确实相等。
其实这是必然的,自己随便写几个样例都是的。

实际应用

在一串数中,求完差分数组,把他从第二项起,每一项都取最大公因数。最后在每次询问中,用任意一个元素加上iii再与前面的gcd⁡\gcdgcd结果再一次进行gcd⁡\gcdgcd,得到的结果就是输出的答案。

Code

#include<bits/stdc++.h>
  using namespace std;
  int a[100001],cf[100001]; //原数组和差分数组
  int gcd(int x,int y) //最大公因数
  {
  return y==0:x:gcd(y,x%y);
  }
  int main()
  {
  int n,q;
  cin>>n>>q; //输入
  for(int i=1;i<=n;i++)cin>>a[i],cf[i]=a[i]-a[i-1]; //输入 计算差分
    int g=cf[2];
    for(int i=3;i<=n;i++)g=gcd(g,cf[i]); //求gcd
    for(int i=1;i<=q;i++)
    {
    cout<<gcd(a[1]+i,g)<<endl; //处理 输出
    }
    return 0;
    }

在这里插入图片描述
然而提交的时候
在这里插入图片描述
没错,n=1n=1n=1时忘了特判,加了特判能AC。

#include<bits/stdc++.h>
  using namespace std;
  int a[100001],cf[100001]; //原数组和差分数组
  int gcd(int x,int y) //最大公因数
  {
  return y==0?x:gcd(y,x%y);
  }
  int main()
  {
  int n,q;
  cin>>n>>q; //输入
  for(int i=1;i<=n;i++)cin>>a[i],cf[i]=a[i]-a[i-1]; //输入 计算差分
    if(n==1)
    {
    for(int i=1;i<=q;i++)cout<<a[1]+i<<endl;
    return 0;
    }
    int g=cf[2];
    for(int i=3;i<=n;i++)g=gcd(g,cf[i]); //求gcd
    for(int i=1;i<=q;i++)
    {
    cout<<gcd(a[1]+i,g)<<endl; //处理 输出
    }
    return 0;
    }

在这里插入图片描述
…个毛啊!
等一下,0分!?自定义评分脚本这么BT吗?
你一不小心把鼠标放到了wa的点上。
在这里插入图片描述
翻译:第3行第一列有字符错误,读取到了-,正确应是1
哦哦哦!忘记处理负数,加个abs就好了!

#include<bits/stdc++.h>
  using namespace std;
  int a[100001],cf[100001]; //原数组和差分数组
  int gcd(int x,int y) //最大公因数
  {
  return y==0?x:gcd(y,x%y);
  }
  int main()
  {
  int n,q;
  cin>>n>>q; //输入
  for(int i=1;i<=n;i++)cin>>a[i],cf[i]=a[i]-a[i-1]; //输入 计算差分
    if(n==1)
    {
    for(int i=1;i<=q;i++)cout<<a[1]+i<<endl;
    return 0;
    }
    int g=cf[2];
    for(int i=3;i<=n;i++)g=gcd(g,cf[i]); //求gcd
    for(int i=1;i<=q;i++)
    {
    cout<<abs(gcd(a[1]+i,g))<<endl; //处理 输出
    }
    return 0;
    }

在这里插入图片描述
啊!终于AC了。

总结

本题对于不会数学的人来说,送你一个词:天方夜谭。
对于只会简单gcd而不知道其中原理而tle的人,送你一个词:一瓶子不满半瓶子晃。
只有会进行细致数学分析和耐心查错的人,才能AC。
为什么5级都是这种数学分析题啊?

posted @ 2025-10-12 09:24  yxysuanfa  阅读(11)  评论(0)    收藏  举报