做这个题之前强烈建议先做hdu5726
https://blog.youkuaiyun.com/Lngxling/article/details/82424842
题意:
把某个位置的数改成另一个数,问所有区间能形成的不同gcd的数量
思路:
如果直接处理一遍所有区间的gcd,复杂度是O(n*logn*logn),再有q个询问,会T,所以要减少处理
当改变i位置的数的时候,会影响的只有包括i的区间,那我们就可以只更改包括i的区间,把原来包含i的区间gcd全部减掉,更改i位置的数后再把所有包含i的区间gcd全部加上
由hdu5726可以知道,每个i位置左侧的gcd最多有logn个,右侧也最多有logn个,所以包含i的不同gcd的区间最多有logn*logn个,时间复杂度上是可行的
所以总体思路是先预处理出所有的gcd的个数和每个gcd的数量,对于每一次的修改,以这个点为右和左端点,分别向左和右查找会形成的不同gcd并存下来,然后修改
详细的可以看注释
#include <iostream>
#include <cstdio>
#include <cmath>
#include <vector>
#include <map>
#include <stack>
#include <set>
#include <string>
#include <cstring>
#include <algorithm>
using namespace std;
#define ll long long
#define max_ 50010
#define mod 100000007
int n,q;
int num[max_];
ll cnt[1000100];
struct node
{
int l,r;
int w;
};
struct node tree[max_*4];
struct nnode
{
int pos;
int w;
nnode(int p_,int w_)
{
pos=p_;w=w_;
}
};
vector<struct nnode>lt,rt;
int casnum=1;
ll ans;
void built(int i,int l,int r)
{
tree[i].l=l;
tree[i].r=r;
if(l==r)
{
tree[i].w=num[l];
return;
}
int mid=(l+r)>>1;
built(i<<1,l,mid);
built(i<<1|1,mid+1,r);
tree[i].w=__gcd(tree[i<<1].w,tree[i<<1|1].w);
}
int query_left(int i,int l,int r,int v)//向左查询
{
if(tree[i].l>r||tree[i].r<l)
return 0;
if(tree[i].l>=l&&tree[i].r<=r&&tree[i].w%v==0)
return 0;
if(tree[i].l==tree[i].r)
return tree[i].l;
int tmp=query_left(i<<1|1,l,r,v);
if(tmp==0)
return query_left(i<<1,l,r,v);
else
return tmp;
}
int query_right(int i,int l,int r,int v)//向右查询
{
if(tree[i].l>r||tree[i].r<l)
return n+1;
if(tree[i].l>=l&&tree[i].r<=r&&tree[i].w%v==0)
return n+1;
if(tree[i].l==tree[i].r)
return tree[i].l;
int tmp=query_right(i<<1,l,r,v);
if(tmp==n+1)
return query_right(i<<1|1,l,r,v);
else
return tmp;
}
void updata(int i,int x,int v)//线段树上点更新
{
if(tree[i].l==tree[i].r)
{
tree[i].w=v;
return;
}
int mid=(tree[i].l+tree[i].r)>>1;
if(x<=mid)
updata(i<<1,x,v);
else
updata(i<<1|1,x,v);
tree[i].w=__gcd(tree[i<<1].w,tree[i<<1|1].w);
}
void init()//预处理所有区间gcd
{
memset(cnt,0,sizeof cnt);//记录每个gcd能被几个区间形成
ans=0;//记录总的不同gcd的个数
for(int i=1;i<=n;i++)
{
int now=num[i];
int pos=i;
while(pos>=1)
{
int k=query_left(1,1,pos,now);
if(cnt[now]==0)
ans++;
cnt[now]+=pos-k;
now=__gcd(now,num[k]);
pos=k;
}
}
}
void solve(int p,int value)
{
lt.clear();
rt.clear();
int pos=p;
int now=num[p];
while(pos>=1)//向左查询
{
int k=query_left(1,1,pos,now);
lt.push_back(nnode(pos-k,now));//分别存储
now=__gcd(now,num[k]);
pos=k;
}
pos=p;
now=num[p];
while(pos<=n)//向右查询
{
int k=query_right(1,pos,n,now);
rt.push_back(nnode(k-pos,now));//分开存储
now=__gcd(now,num[k]);
pos=k;
}
for(auto i:lt)
{
for(auto j:rt)//遍历每个左和右的gcd
{
int w=__gcd(i.w,j.w);//总的gcd是左侧的gcd与右侧gcd再取gcd
int c=j.pos*i.pos;//总的个数是左侧数量乘右侧数量 即所有会产生这个gcd的区间的个数
if(value==1&&cnt[w]==0)
ans++;//新出现了就就加上
cnt[w]+=value*c;
if(value==-1&&cnt[w]==0)
ans--;//被减没了就减去
}
}
}
int main(int argc, char const *argv[]) {
int t;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&q);
for(int i=1;i<=n;i++)
scanf("%d",&num[i]);
built(1,1,n);
init();
printf("Case #%d:\n",casnum++);
int p,v;
while(q--)
{
scanf("%d%d",&p,&v);
solve(p,-1);//把原本有的减去
num[p]=v;
updata(1,p,v);//修改
solve(p,1);//把新形成的加上
printf("%lld\n",ans );
}
}
return 0;
}