题目链接
题面
题面翻译
有
T
T
T 组询问,每次给出
n
n
n 个数
a
i
a_i
ai。
你需要找到这个数组的一个子序列(要求编号连续),使得该序列中所有数的最大公约数和序列长度的乘积最大,并输出这个最大值。
题目描述
The Magical GCD of a nonempty sequence of positive integers is defined as the product of its length and the greatest common divisor of all its elements.
Given a sequence
(
a
1
,
…
,
a
n
)
(a_1, \ldots , a_ n)
(a1,…,an), find the largest possible Magical GCD of its connected subsequences.
输入格式
The first line of input contains the number of test cases
T
T
T. The descriptions of the test cases follow:
The description of each test case starts with a line containing a single integer
n
n
n,
1
≤
n
≤
100
000
1 \leq n \leq 100\, 000
1≤n≤100000. The next line contains the sequence
a
1
,
a
2
,
…
,
a
n
a_1, a_2 , \ldots , a _ n
a1,a2,…,an,
1
≤
a
i
≤
1
0
12
1 \leq a_ i \leq 10^{12}
1≤ai≤1012.
输出格式
For each test case output one line containing a single integer: the largest Magical GCD of a connected subsequence of the input sequence.
样例 #1
样例输入 #1
1
5
30 60 20 20 20
样例输出 #1
80
提示
Time limit: 8000 ms, Memory limit: 1048576 kB.
Central Europe Regional Contest (CERC) 2013
题意
在长度为 n n n的区间内选一个连续的子序列,要求子序列内所有的值的 g c d gcd gcd乘以子序列长度最大,输出这个值
思路
O ( n 3 ) O(n^3) O(n3)暴力
很简单,第一重循环枚举左端点,第二重循环枚举右端点,第三重循环求枚举区间内所有值的 g c d gcd gcd,更新答案,做完(时间复杂度 O ( n 3 ) O(n^3) O(n3) )
O ( n 2 ) O(n^2) O(n2)暴力+树状数组
对于 O ( n 3 ) O(n^3) O(n3)暴力,我们可以对求子序列的 g c d gcd gcd进行树状数组预处理
由于求区间 g c d gcd gcd是一种可重复贡献问题,所以求值时可以将区间分成多份,所以我们可以使树状数组进行特定区间预处理
通过树状数组优化第三重循环后,时间复杂度变成了 O ( n 2 ) O(n^2) O(n2)
void build(){//树状数组预处理
for(int j=1;j<=21;j++)
for(int i=1;i+(1<<j)-1<=n;i++)
st[i][j]=__gcd(st[i][j-1],st[i+(1<<(j-1))][j-1]);
return;
}
int query(int l,int r){//树状数组询问区间l~r的gcd
int tmp=llog[r-l+1];
return __gcd(st[l][tmp],st[r-(1<<tmp)+1][tmp]);
}
AC做法
因为
n
n
n达到
1
0
6
10^6
106的大小,导致
O
(
n
2
)
O(n^2)
O(n2)的做法会
T
L
E
TLE
TLE
这时我们来推一遍样例
5
30 60 20 20 20
先设当前区间的 g c d gcd gcd为 p p p
i = 1 i=1 i=1时
j = 1 j=1 j=1时, p = 30 p=30 p=30
j = 2 j=2 j=2时, p = 30 p=30 p=30
j = 3 j=3 j=3时, p = 10 p=10 p=10
j = 4 j=4 j=4时, p = 10 p=10 p=10
j = 5 j=5 j=5时, p = 10 p=10 p=10i = 2 i=2 i=2时
j = 2 j=2 j=2时, p = 60 p=60 p=60
j = 3 j=3 j=3时, p = 20 p=20 p=20
j = 4 j=4 j=4时, p = 20 p=20 p=20
j = 5 j=5 j=5时, p = 20 p=20 p=20i = 3 i=3 i=3时
j = 3 j=3 j=3时, p = 20 p=20 p=20
j = 4 j=4 j=4时, p = 20 p=20 p=20
j = 5 j=5 j=5时, p = 20 p=20 p=20i = 4 i=4 i=4时
j = 4 j=4 j=4时, p = 20 p=20 p=20
j = 5 j=5 j=5时, p = 20 p=20 p=20i = 5 i=5 i=5时
j = 5 j=5 j=5时, p = 20 p=20 p=20
可以发现,随着
j
j
j的右移,所对应的
p
p
p也呈不上升的有序分布
什么是分布,观察当
i
=
1
i=1
i=1的
p
p
p
显而易见,
p
p
p呈块状分布且具有递减性
而且如果要想让当前区块
g
c
d
gcd
gcd乘以区块长度最大,那就必须让右端点指向当前区块的末端点,所以我们将问题转化为了求区块的末端点
思考一下,有序的数列快速搜索一个元素
这不就是二分查找吗?!
对于每一个
i
i
i,我们不停地二分查找
j
∼
n
j\sim n
j∼n的区块的末端点,每次查找完就将
j
j
j指向当前区块末端点加一的下标,继续查找,直到
j
j
j超出
n
n
n
细节见代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
int llog[100010]={-1},st[100010][22],j,l,r,k,mid,tmp,ans,t,n;
void build(){//树状数组预处理
for(int j=1;j<=21;j++)
for(int i=1;i+(1<<j)-1<=n;i++)
st[i][j]=__gcd(st[i][j-1],st[i+(1<<(j-1))][j-1]);
return;
}
int query(int l,int r){//树状数组询问区间l~r的gcd
tmp=llog[r-l+1];
return __gcd(st[l][tmp],st[r-(1<<tmp)+1][tmp]);
}
signed main(){
scanf("%lld",&t);
for(int i=1;i<=100000;i++)llog[i]=llog[i>>1]+1;//log预处理
while(t--){
scanf("%lld",&n);
for(int i=1;i<=n;i++)scanf("%lld",&st[i][0]);
build();//预处理树状数组
ans=-0x7fffffff;
for(int i=1;i<=n;i++){
j=i;//j从i开始
while(j<=n){
l=i,r=n+1;//初始化l,r
k=query(i,j);//上一个区块的gcd,作为二分查找压缩的参考
while(l+1<r){
mid=l+r>>1;
if(query(i,mid)<k)r=mid;//如果i~mid的区间gcd小于参考值,则认为mid下标大于下一个区块末端的下标,向左继续二分
else l=mid;
}
ans=max(ans,k*(l-i+1));//更新答案
j=r+1;
}
}
printf("%lld\n",ans);
}
return 0;
}