题目
给你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;
}