题意:询问一个序列的某一个子区间中的最大的gcd的值。
这题关键一点是把最大的gcd转换为最大出现过多次的约数,这样就可以转化为一个求区间最大值的问题了。
我从后往前扫一遍原序列,同时记录每一个约数出现的最靠左的位置。每次处理序列中的一个数时,对这个数所有的约数,找到该约数出现的最靠左的位置,并在树状数组中更新该位置的值。在我们处理序列的第i的元素的时候,对于起始位置在i的询问,我们只要求出树状数组[0, r]区间的最大值就可以了。
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 50000 + 10;
int num[maxn], n;
int hash[maxn];
int c[maxn];
int ans[maxn], nq;
struct query
{
int l, r, ord;
bool operator < (const query & rhs) const { return l > rhs.l; }
}q[maxn];
inline int lowbit(int t) { return t & (-t); }
inline void add(int pos, int t) { while(pos <= n) { c[pos] = max(c[pos], t); pos += lowbit(pos); } }
inline int sum(int pos) { int s = 0; while(pos > 0) { s = max(s, c[pos]); pos -= lowbit(pos); } return s; }
void prework()
{
scanf("%d", &n);
for(int i = 1; i <= n; ++i) c[i] = 1;
memset(hash, 0, sizeof(hash));
for(int i = 1; i <= n; ++i) scanf("%d", num + i);
scanf("%d", &nq);
for(int i = 1; i <= nq; ++i)
{
scanf("%d %d", &q[i].l, &q[i].r);
q[i].ord = i;
}
sort(q + 1, q + nq + 1);
}
void solve()
{
int c = 1;
for(int i = n; i > 0; --i)
{
for(int j = 1; j * j <= num[i]; ++j)
{
if(num[i] % j == 0)
{
if(hash[j] != 0) add(hash[j], j);
hash[j] = i;
if(j * j != num[i])
{
int tmp = num[i] / j;
if(hash[tmp] != 0) add(hash[tmp], tmp);
hash[tmp] = i;
}
}
}
while(q[c].l == i)
{
ans[q[c].ord] = sum(q[c].r);
if(q[c].l == q[c].r) ans[q[c].ord] = 0;
c++;
}
}
for(int i = 1; i <= nq; ++i) printf("%d\n", ans[i]);
}
int main()
{
freopen("in.txt", "r", stdin);
int t; cin >> t;
while(t--)
{
prework();
solve();
}
return 0;
}