HDU 5726 GCD
题意
给连续N个数,求[l,r]区间的GCD,给出M次查询,查出和[l,r]的GCD相同的区间有多少。
N、M都是10W级别。
解题思路
记得当时分析出来从i开始的一串GCD单调不减,GCD为1后一直为1,简单贪心了终结为1的位置,显然超时。
那么正确解法为用RMQ维护[l,r]的GCD,然后用二分查找,依次查出从左端点i开始的具有相同GCD的右端点范围,然后用map累加起来。
借鉴这个博客的思路。
代码
#include <cstdio>
#include <algorithm>
#include <map>
#include <cmath>
using namespace std;
int f[100010][18];
int a[100010];
int n, m;
int __gcd(int a, int b)
{
int t;
while(b)
{
t = a % b;
a = b;
b = t;
}
return a;
}
//动规思想,二分线段树思想,O(nlogn)计算连续段的GCD
//f[i][j]表示i开始2^j个的GCD
void rmq()
{
for (int i = 1; i <= n; ++i)
{
f[i][0] = a[i];
}
for (int i = 1; i < 18; ++i)
{
for (int j = 1; j + (1 << i) - 1 <= n; ++j)
{
f[j][i] = __gcd(f[j][i - 1], f[j + (1 << (i - 1))][i - 1]);
}
}
}
map<int, long long> mp;
//利用从i开始的一组数字的GCD随着数字增多单调不增,二分计算GCD数目
//插入Map
int findgcd(int l, int r)
{
int k = (int)log2((double)(r - l + 1));
return __gcd(f[l][k], f[r - (1 << k) + 1][k]);
}
void setTable()
{
mp.clear();
for (int i = 1; i <= n; ++i)//从i开始
{
int curgcd = f[i][0];
int j = i;
while(j <= n)
{
int l = j, r = n;
while(l < r)
{
int mid = (l + r + 1) >> 1;
if (findgcd(i, mid) == curgcd) l = mid;
else r = mid - 1;
}
mp[curgcd] += l - j + 1;
j = l + 1;//计算一个更小的GCD
curgcd = findgcd(i, j);
}
}
}
int main(int argc, char const *argv[])
{
int T;
scanf("%d", &T);
for(int cas = 1; cas <= T; ++cas)
{
printf("Case #%d:\n", cas);
scanf("%d", &n);
for (int i = 1; i <= n; ++i)
{
scanf("%d", &a[i]);
}
rmq();
setTable();
scanf("%d", &m);
for (int i = 0; i < m; ++i)
{
int l, r;
scanf("%d %d", &l, &r);
int curgcd = findgcd(l, r);
printf("%d %I64d\n", curgcd, mp[curgcd]);
}
}
return 0;
}