hdu5930 GCD(线段树+二分+gcd)

题目

给你n(n<=5e4)个数,第i个数为ai(1<=ai<=1e6)

以下q(q<=5e4)个修改,第j次把pj改为vj(1<=vj<=1e6)

每次询问问修改之后,[1,n]间有多少种不同的gcd的值

思路来源

归神代码

题解

网上搜题解都看不懂,只好硬啃代码自己再写一份

首先,对于每个以i为右端点的ai来说,初始令l==r

在l向左移动的过程中,[l,r]区间gcd的值最多会改变log(ai)次

因为每次改变,ai最少会除以2,所以log次就会变成1,

也就是说对于每个右端点,有log个不同的gcd值,每个gcd值对应一段

 

num[i]记录gcd为i的值是多少个[L,R]区间的gcd

 

枚举右端点r,当前位置为tmp,当前gcd为now

每次向左二分,找到gcd不为当前gcd的值的第一个位置pos,其gcd值为next

那么[pos+1,tmp]的gcd值就为now,gcd==now的个数为tmp-pos

然后再令,now=next,tmp=pos,重复该过程直至到左端点

 

askl函数即是上述二分的具体实现函数,

每次找到最右的第一个gcd不为当前gcd的值,

 

一、如果当前区间[l,r]已经在pos以左,

①这一段区间的gcd是now的倍数,说明整段区间都不是结果,不用下溯,直接返回一个不影响答案的数对

数对只需保证第一维是一个大于等于gcd的值即可,根本不会去看第二维

②如果gcd不是now的倍数,且递归到叶子结点,说明该叶子结点即是分界,

对其求gcd即得到所需的答案

二、当前[l,r]包含pos,又分为两种情况

①pos在左半部分,那么直接查询左子树

②pos在右半部分,考虑到tmp可能在右子树,优先去右子树查

如果右子树查到的gcd不比now小,说明位置不可能在右子树里,继续去左子树查

否则返回在右子树查询到的对应数对

 

注意dat[p]%now==0判gcd的写法,可能中间查询一段的时候dat[p]是now的倍数,

但是当dat[p]和now在一段区间里的时候,它们的gcd就是now了

并不仅仅是dat[p]==now的情形

 

维护gcd的修改和区间的查询,是裸的线段树操作

心得

如果在外面二分的话,每次查询区间gcd的值,写法就是两个log的了

线段树里用函数二分的写法还是要掌握的鸭

理解二分的过程理解了3h,自己敲代码又敲了1h

还好,debug的时间不久,输出了一下中间结果调调参就A了

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll; 
typedef pair<int,int> P;
const int maxn=5e4+10;
const int maxv=1e6+10;
int t,n,q;
int ans;
int a[maxn],dat[maxn*5];
int num[maxv];
int pos,tmp,v;
int now,final;
int leni,lenj,numl,numr,gcdl,gcdr,g;
void pushup(int p)
{
	dat[p]=__gcd(dat[p<<1],dat[p<<1|1]);
}
void build(int p,int l,int r)
{
	if(l==r)
	{
		dat[p]=a[l];
		return;
	}
	int mid=(l+r)/2;
	build(p<<1,l,mid);
	build(p<<1|1,mid+1,r);
	pushup(p);
}
void update(int p,int l,int r,int pos,int v)
{
	if(l==r)
	{
		dat[p]=v;
		return;
	}
	int mid=(l+r)/2;
	if(pos<=mid)update(p<<1,l,mid,pos,v);
	else update(p<<1|1,mid+1,r,pos,v);
	pushup(p);
}
int ask(int p,int l,int r,int ql,int qr)
{
	if(ql<=l&&r<=qr)return dat[p];
	int res=0;
	int mid=(l+r)/2;
	if(ql<=mid)res=__gcd(res,ask(p<<1,l,mid,ql,qr));
	if(qr>mid)res=__gcd(res,ask(p<<1|1,mid+1,r,ql,qr));
	return res;
}
P askl(int p,int l,int r,int pos,int now)//向左找到第一个gcd不为now的值
{
	if(r<=pos)
	{
		if(dat[p]%now==0)return P(now,-1);//第一维代表当前这段不是要找的值 不用再下溯了 
		else if(l==r)return P(__gcd(now,a[l]),l);//找到了值 第二维说明了位置 
	}
	int mid=(l+r)/2;
	if(pos<=mid)return askl(p<<1,l,mid,pos,now);//如果当前位置在左半边 左子树 
	P tmp=askl(p<<1|1,mid+1,r,pos,now);//当前位置在右半边 优先找右子树 
	if(tmp.first<now)return tmp;//答案在右子树里 
	return askl(p<<1,l,mid,pos,now);//答案在左子树里 
} 
P askr(int p,int l,int r,int pos,int now)
{
	if(l>=pos)
	{
		if(dat[p]%now==0)return P(now,-1);
		else if(l==r)return P(__gcd(now,a[l]),l);//找到了值 第二维说明了位置 
	}
	int mid=(l+r)/2;
	if(pos>mid)return askr(p<<1|1,mid+1,r,pos,now);
	P tmp=askr(p<<1,l,mid,pos,now);
	if(tmp.first<now)return tmp;
	return askr(p<<1|1,mid+1,r,pos,now);
}
void output()
{
	for(int i=1;i<maxv;++i)
	if(num[i]>0)printf("num[%d]:%d\n",i,num[i]);
}
void solve(int pos,int op)
{
	vector<P>pl,pr;
	now=a[pos];tmp=pos;
	final=ask(1,1,n,1,pos);
	pl.push_back(P(now,pos));
	while(now!=final)
	{
		P to=askl(1,1,n,tmp,now);
		pl.push_back(to);
		now=to.first;
		tmp=to.second; 
	}
	pl.push_back(P(final,0));
	now=a[pos];tmp=pos;
	final=ask(1,1,n,pos,n);
	pr.push_back(P(now,pos));
	while(now!=final)
	{
		P to=askr(1,1,n,tmp,now);
		pr.push_back(to);
		now=to.first;
		tmp=to.second;
	}
	pr.push_back(P(final,n+1));
	leni=pl.size();lenj=pr.size();
	for(int i=1;i<leni;++i)
	{
		numl=pl[i-1].second-pl[i].second;
		gcdl=pl[i-1].first; 
		for(int j=lenj-1;j>=1;--j)
		{
			numr=pr[j].second-pr[j-1].second;
			gcdr=pr[j-1].first;
			g=__gcd(gcdl,gcdr);
			if(!num[g])ans++;
			num[g]+=op*numl*numr;
			if(!num[g])ans--;
		}
	}
	//puts(op<0?"删:":"加");
	//output();
}
int main()
{
	scanf("%d",&t);
	for(int cas=1;cas<=t;++cas)
	{
		ans=0;
		memset(num,0,sizeof num);
		scanf("%d%d",&n,&q);
		for(int i=1;i<=n;++i)
		scanf("%d",&a[i]);
		printf("Case #%d:\n",cas);
		build(1,1,n);
		for(int r=1;r<=n;++r)
		{
			now=a[r];pos=r;
			final=ask(1,1,n,1,r);
			while(now!=final)
			{
				P to=askl(1,1,n,pos,now);
				if(!num[now])ans++;
				num[now]+=pos-to.second;
				now=to.first;pos=to.second;
			}
			if(!num[final])ans++;
			num[final]+=pos;
		}
		//output();
		for(int i=1;i<=q;++i)
		{
			scanf("%d%d",&pos,&v);
			solve(pos,-1);
			a[pos]=v;
			update(1,1,n,pos,v);
			solve(pos,1);
			printf("%d\n",ans);
		}
	}
	return 0;
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Code92007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值