gcd + rmq + 二分遍历 hdu 5726

本文介绍了一种高效算法,用于求解给定序列上指定区间内的最大公约数(GCD),并统计与该GCD相等的区间数量。通过预处理和分层二分搜索,实现了快速查询。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意:
 

给含有n个数的序列,现在询问[l,r]的gcd是多少,同时还要求与gcd相同的区间有多少个;


思路:
 
静态区间可以用rmq,先预处理就可以O(1)的回答询问了;还有就是询问区间的个数,可以先预处理出所有的gcd,用map存下来,预处理的时候枚举左端点,由于gcd是单调递减的,所以可以分层后二分右端点,找到每层的长度,加到map中,由于一个数的因子个数不超过log(n)所以层数比较少才可以这样处理.
#include<cstdio>
#include<algorithm>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<cmath>
#include<string>
#include<map>
#define INF 99999999
#define LL long long
#define maxn 100005
using namespace std;
int n,T,q,l,r;
int a[maxn];
int qq[maxn][2];
int f[maxn][20];//dp数组,存储以第i个数开始,到第i+(1<<j)-1个数之间区间的gcd
map<int,LL>mm;//记录某个gcd的值出现的次数
int gcd(int a,int b)
{
	if(b==0)
		return a;
	return gcd(b,a%b);
}
void init()
{
	for(int i=1;i<=n;i++)
	{
		f[i][0]=a[i];
	}
	for(int j=1;j<=20;j++)//注意这里j从1开始,因为前面已经初始化了放f[i][0] 
	{
		for(int i=1;i+(1<<j)-1<=n;i++)
		{
			f[i][j]=gcd(f[i][j-1],f[i+(1<<(j-1))][j-1]);
		}
	}
}
int query(int l,int r)
{
	if(l>r)
		swap(l,r);
	int k=(int)(log((double)(r-l+1))/log(2.0));
	return gcd(f[l][k],f[r-(1<<k)+1][k]);
}
void solve()//固定每个左端点,二分遍历右端点 
{
	int l,r,mid;
	for(int i=1;i<=n;i++)//枚举每个左端点
	{
		int val=a[i];
		int pos=i;
		while(pos<=n)//由于gcd是单调递减的,所以可以分层后二分右端点,由于一个数的因子个数不超过log(n)所以层数比较少才可以这样处理
		{// 2 4 6 9 12 18 28 40 这组数据 2 4 6 为一层,过了这一层pos变为4 (每一层的gcd不同) 
			val=query(i,pos);
			l=pos;r=n;//确定二分遍历右端点的区间 
			while(l<=r)
			{
				mid=(l+r)/2;
				if(query(i,mid)==val)
					l=mid+1;
				else
					r=mid-1;
			}
			mm[val]+=(r-pos+1);
			pos=l;
		}
	}
}
int main()
{
	scanf("%d",&T);
	int cnt=1;
	while(T--)
	{
		scanf("%d",&n);
		for(int i=1;i<=n;i++)
		{
			scanf("%d",&a[i]);
		}
		init();
		mm.clear();
		solve();
		scanf("%d",&q);
		for(int i=1;i<=q;i++)
		{
			scanf("%d %d",&l,&r);
			qq[i][0]=l;
			qq[i][1]=r;
		}
		printf("Case #%d:\n",cnt++);
		for(int i=1;i<=q;i++)
		{
			printf("%d %lld\n",query(qq[i][0],qq[i][1]),mm[query(qq[i][0],qq[i][1])]);
		}
	}
	return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值