(多校第一场1004)HDU5726 GCD(区间GCD查询+)

题意:给出n个数,区间为(1,n),查询区间(l,r)的GCD,以及区间(1, n)有多少子区间的GCD等于区间(l,r)的GCD。

做法:使用线段树,或RMQ维护区间GCD,并且要预处理出所有可能出现的GCD值 的区间数量。针对固定左端点,一直向右GCD阶梯状减小的特性,大概有两种做法。

1)对于某一固定起点(以选定左端点L=i为例),从最右端R开始,二分枚举GCD突变位置pos,这一段阶梯的长度就是R-pos+1,定义INTERGCD(L,R)=区间(L,R)的GCD,则GCD值等于 INTERGCD(L,R)的区间数量就增加R-pos+1。

对于左端点i:令L=i,R=n。

a、l=L,r=R。

b、m=(l+r)/2,如果INTERGCD(L,m)<=INTERGCD(L,R),r=m;否则l=m+1;如果l==r,则阶梯突变位置就是l或r,记录pos=l,转c;否则重复b;

c、阶梯长度就是pos-R+1,则_map[INTERGCD(L,R)]+=pos-R+1;令R=pos-1,如果R>=L,转a,计算下一个阶梯;否则,以i为左端点的阶梯就计算完了。


2)以arr=(1,2,4,6,7)这列数为例,如图,数列下面是以某个i为端点的所有的GCD值,_tmp和_tmpnext是值为<GCD,数量>的map,_tmp记录当前列,各个GCD值的数量,_tmpnext记录下一行的_tmp值,_tmpnext由_tmp推得;对于第i-1列的_tmp的每一个元素iter(指针),_tmpnext[GCD(arr[i],iter->first)]+=iter->second。图中给出了两列所对应的_tmp(_tmpnext)的值,(可能第四列到第五列理解更清晰)。

                                                                                  



对于第一种写法,因为在预处理的时候要询问INTERGCD(l,r),所以用线段树,预处理的时间复杂度就成了O(n(logn)^3),就会超时。用RMQ,O(1)的查询就可以了。第二种写法在处理的时候不需要询问INTERGCD,就无所谓了。

附上两种写法:calcu_interval是第一种写法,progress是第二种。

RMQ

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
#include <map>
#include <string>
#include <cmath>
#define maxn 110000
#define LL long long
using namespace std;

int arr[maxn];
int _gcd[maxn][30];
map<int,LL>_map;

int _query(int l,int r)
{
	int len=r-l+1;
	int temp=(int)log2(double(len));
	return __gcd(_gcd[l][temp],_gcd[r-(1<<temp)+1][temp]);
}

int  _find(int l,int r,int n)
{
	int gcd=_query(l,r);
	int L=l,R=r;
	while(l!=r)
	{
		int m=(l+r)/2;
		if(_query(L,m)<=gcd)
		{
			r=m;
		}
		else
			l=m+1;
	}
	int len=R-l+1;
	_map[gcd]+=len;
	return l;
}
void calcu_interval(int n)
{
	_map.clear();
	for(int i=1;i<=n;++i)
	{
		int l=i,r=n;
		int pos=_find(l,r,n);
		while(pos!=i)
		{
			r=pos-1;
			pos=_find(l,r,n);
		}
	}
}
map<int,LL>_tmp,_tmpnext;
map<int,LL>::iterator iter;
void progress(int n)
{
	_map.clear();
	_tmp.clear();
	for(int i=1;i<=n;++i)
	{
		_tmpnext.clear();
		_tmpnext[arr[i]]++;
		for(iter=_tmp.begin();iter!=_tmp.end();iter++)
		{
			int now=__gcd(arr[i],iter->first);
			_tmpnext[now]+=iter->second;
		}
		for(iter=_tmpnext.begin();iter!=_tmpnext.end();iter++)
		{
			_map[iter->first]+=iter->second;
		}
		_tmp=_tmpnext;
	}
}

int main()
{
	int T,I=1;
	scanf("%d",&T);
	while(T--)
	{
		int n;
		scanf("%d",&n);
		for(int i=1;i<=n;++i)
		{
			scanf("%d",&arr[i]);
			_gcd[i][0]=arr[i];
		}
		int nl=(int)log2(double(n));
		for(int j=1;j<=nl;++j)
		{
			for(int i=1;i<=n;++i)
			{
				if(i+(1<<(j-1))<=n)
				{
					_gcd[i][j]=__gcd(_gcd[i][j-1],_gcd[i+(1<<(j-1))][j-1]);
				}
			}
		}
//		calcu_interval(n);
		progress(n);
		int m;
		scanf("%d",&m);
		printf("Case #%d:\n",I++);
		for(int i=0;i<m;++i)
		{
			int l,r;
			scanf("%d%d",&l,&r);
			int gcd=_query(l,r);
			printf("%d %lld\n",gcd,_map[gcd]);
		}
	}
}


线段树

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
#include <map>
#include <string>
#include <cmath>
#define maxn 110000
#define LL long long
using namespace std;

int arr[maxn];
struct seg
{
    int l,r;
    int gcd;
    seg *left,*right;
};
struct node
{
    int v,w;
};
vector<node>head[maxn];
map<int,LL>_map;
seg  *build(int l,int r)
{
    int m=(l+r)/2;
    seg *rt=new seg;
    rt->l=l;
    rt->r=r;
    if(l==r)
    {
        rt->left=rt->right=NULL;
        rt->gcd=arr[l];
        return rt;
    }
    rt->left=build(l,m);
    rt->right=build(m+1,r);
    rt->gcd=__gcd(rt->left->gcd,rt->right->gcd);
    return rt;
}
int query(seg *rt,int ql,int qr)
{
    if(ql<=rt->l && rt->r<=qr)
    {
        return rt->gcd;
    }
    int l=rt->l;
    int r=rt->r;
    int m=(l+r)/2;
    int gcd=0;
    if(ql<=m)
        gcd=query(rt->left,ql,qr);
    if(qr>m)
        gcd=(gcd==0)?query(rt->right,ql,qr):__gcd(gcd,query(rt->right,ql,qr));
    return gcd;
}

map<int,LL>_tmp,_tmpnext;
map<int,LL>::iterator iter;
void progress(int n)
{
	_map.clear();
	_tmp.clear();
	for(int i=0;i<n;++i)
	{
		_tmpnext.clear();
		_tmpnext[arr[i]]++;
		for(iter=_tmp.begin();iter!=_tmp.end();iter++)
		{
			int now=__gcd(arr[i],iter->first);
			_tmpnext[now]+=iter->second;
		}
		for(iter=_tmpnext.begin();iter!=_tmpnext.end();iter++)
		{
			_map[iter->first]+=iter->second;
		}
		_tmp=_tmpnext;
	}
}
void del(seg *rt)
{
    if(rt!=NULL)
    {
        del(rt->left);
        del(rt->right);
        delete rt;
    }
}
int main()
{
    int T,I=1;
    scanf("%d",&T);
    while(T--)
    {
        int n;
        scanf("%d",&n);
        for(int i=0;i<n;++i)
        {
            scanf("%d",&arr[i]);
        }
        seg *root=build(0,n-1);
        for(int i=0;i<n;++i)
        {
            head[i].clear();
        }
        progress(n);
        int m;
        scanf("%d",&m);
        printf("Case #%d:\n",I++);
        for(int i=0;i<m;++i)
        {
            int l,r;
            scanf("%d%d",&l,&r);
            int gcd=query(root,l-1,r-1);
            printf("%d %lld\n",gcd,_map[gcd]);
        }
        del(root);
    }
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值