HDU - 5930 GCD —— 线段树gcd

本文介绍了一种高效算法,用于解决在数组中修改某一元素后,所有子区间可能产生的不同最大公约数(GCD)数量的变化问题。通过预处理及线段树结构,实现了O(log^2 n)的时间复杂度。

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

做这个题之前强烈建议先做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;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值