[P12786] Greatest of the Greatest Common Divisors
题目链接
来源:The 2024 ICPC Asia Yokohama Regional Contest I - Greatest of the Greatest Common Divisors
题目描述
给定一个整数序列和该序列上的若干区间。这些区间由其最左位置和最右位置指定。一个包含 \(k\) 个整数的区间具有 \(k(k - 1)/2\) 个位于不同位置的数对,每个数对有其最大公约数。 对于每个给定的区间,找出所有这些最大公约数中最大的一个。
例如,当序列为 \((a_1, \ldots, a_6) = (10, 20, 30, 40, 50, 60)\),且询问区间为整个序列时,需要考虑以下 \(15\) 个位于不同位置 \(x\) 和 \(y\) 的两个整数组成的数对及其最大公约数:
| \(x\) | \(1\) | \(1\) | \(1\) | \(1\) | \(1\) | \(2\) | \(2\) | \(2\) | \(2\) | \(3\) | \(3\) | \(\textbf{3}\) | \(4\) | \(4\) | \(5\) |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| \(y\) | \(2\) | \(3\) | \(4\) | \(5\) | \(6\) | \(3\) | \(4\) | \(5\) | \(6\) | \(4\) | \(5\) | \(\textbf{6}\) | \(5\) | \(6\) | \(6\) |
| \(a_x\) | \(10\) | \(10\) | \(10\) | \(10\) | \(10\) | \(20\) | \(20\) | \(20\) | \(20\) | \(30\) | \(30\) | \(\textbf{30}\) | \(40\) | \(40\) | \(50\) |
| \(a_y\) | \(20\) | \(30\) | \(40\) | \(50\) | \(60\) | \(30\) | \(40\) | \(50\) | \(60\) | \(40\) | \(50\) | \(\textbf{60}\) | \(50\) | \(60\) | \(60\) |
| \(\gcd(a_x,a_y)\) | \(10\) | \(10\) | \(10\) | \(10\) | \(10\) | \(10\) | \(20\) | \(10\) | \(20\) | \(10\) | \(10\) | \(\textbf{30}\) | \(10\) | \(20\) | \(10\) |
在此例中,这 \(15\) 个数对的最大公约数中的最大值为 \(\gcd(30, 60) = 30\)。
输入格式
仅一组数据,格式如下所示:
\(n\)
\(a_1\) \(\cdots\) \(a_n\)
\(q\)
\(l_1\) \(r_1\)
\(\cdots\)
\(l_q\) \(r_q\)
第一行包含一个整数 \(n\),表示给定序列中整数的个数,满足 \(2 \leq n \leq 10^5\)。第二行包含 \(n\) 个正整数 \(a_1\) 到 \(a_n\),指定该序列。其中每个数均不超过 \(10^5\)。
第三行包含一个正整数 \(q\),指定要考虑的序列区间数量,其值不超过 \(10^5\)。 随后是 \(q\) 行,每行指定序列中一个要考虑的区间。其中第 \(i\) 行包含两个整数 \(l_i\) 和 \(r_i\) \((1 \leq l_i < r_i \leq n)\),指定序列中从 \(a_{l_i}\) 到 \(a_{r_i}\) 的区间。
输出格式
输出 \(q\) 行,其中第 \(i\) 行应包含在 \(l_i\) 和 \(r_i\) 指定的区间内所有数对的最大公约数中的最大值。
解题思路
很显然,这道题要是直接求 gcd 一定是解不出来的。
想一想,\(a\) 和 \(b\) 的公约数定义是什么?既是 \(a\) 的约数,又是 \(b\) 的约数的数。既然和约数有关,那我们可以每个约数都开一个桶,存所有含有这个约数的数,这样,如果 \(a\) 和 \(b\) 在同一个约数的桶内,那么这个约数就是 \(a\),\(b\) 的公约数。
问题转化为:给定区间 \([l,r]\),求最大的 \(k\) 使得 \(k\) 的桶中有两个数在 \([l,r]\) 中。
发现这样做无论时间还是空间都得爆炸,考虑优化。
既然要求桶中有两个数在 \([l,r]\) 中,要考虑上界和下界,那如果我只把 \([1,r]\) 的数塞进桶里,这样就只用考虑下界了。为了做到这一点,我们将询问离线,按 \(r\) 从小到大排序,对于询问 \([l_i,r_i]\),只需在前一问的基础上把 \([r_{i-1}+1,r_i]\) 的数塞到桶里,就可以保证上界了。
再考虑下界。在上界一定满足的情况下,如果一个桶中的最大和次大数都不小于下界,那么这个桶就符合条件,否则就一定不符合条件。因此,对于每个桶,我们只需存储最大值和次大值即可,空间的问题也解决了。
现在上下界都满足了,考虑最大的符合条件的桶。我们对每个桶的次大值建一个树状数组或线段树,维护后缀最大值。二分答案 \(mid\),如果后缀 \([mid,m]\)(\(m\) 为最大的 \(a_i\))的最大次大值都小于 \(l\),那么 \([mid,m]\) 中的桶一定都不满足下界的要求,继续在 \([1,mid-1]\) 中查找,否则说明 \([mid,m]\) 中有至少一个桶符合要求,为找到最大的桶,应在 \([mid,m]\) 中继续查找。
维护上界复杂度为 \(O(m\sqrt{m}+(\sum_{i=1}^{m}\tau(i))\log m)\),二分查找答案复杂度为 \(O(\log^2m)\),总时间复杂度为 \(O(m\sqrt{m}+(\sum_{i=1}^{n}\tau(a_i))\log m+q\log^2m)\),其中 \(\tau(a_i)\) 最大为 \(144\),所以复杂度为 \(O(n\sqrt{m}+144n\log m+q\log^2m)\),可以过。
参考代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,q,m,a[150005],l1[150005],l2[150005];
struct node {int id,l,r,ans;} Q[150005];
int t[150005];
int lowbit(int x){return x&(-x);}
int query(int pos)
{
int res=0;
while(pos)
{
res=max(res,t[pos]);
pos-=lowbit(pos);
}
return res;
}
void modify(int pos,int k)
{
while(pos<=m)
{
t[pos]=max(t[pos],k);
pos+=lowbit(pos);
}
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]),m=max(m,a[i]);
scanf("%d",&q);
for(int i=1;i<=q;i++)
{
scanf("%d%d",&Q[i].l,&Q[i].r);
Q[i].id=i;
}
sort(Q+1,Q+q+1,[](node a,node b){return a.r<b.r;});
for(int i=1,r=1;i<=q;i++)
{
while(r<=Q[i].r)
{
for(int i=1;i*i<=a[r];i++)
{
if(a[r]%i!=0) continue;
else l2[i]=l1[i],l1[i]=r,modify(m-i+1,l2[i]);
int j=a[r]/i;
if(i==j) continue;
if(!l1[j]) l1[j]=r;
else l2[j]=l1[j],l1[j]=r,modify(m-j+1,l2[j]);
}
r++;
}
int L=1,R=m;
while(L<R)
{
int mid=(L+R+1)/2;
if(query(m-mid+1)>=Q[i].l) L=mid;
else R=mid-1;
}
Q[i].ans=L;
}
sort(Q+1,Q+q+1,[](node a,node b){return a.id<b.id;});
for(int i=1;i<=q;i++) printf("%d\n",Q[i].ans);
return 0;
}
浙公网安备 33010602011771号