hdu 5381 线段树区间合并

gcd运算的时候 如果之前的保持不变 新加入一个数进行gcd运算 得到的答案只会越来越小

这题要我们求[l.r]中所有子区间的gcd值之和 我们就要尽量保存子区间的gcd在合并区间的时候以尽可能快的速度合并

我们可以知道gcd的数量是log级别的

所以我们可以保存Rg[32],Rn[32],Rc,分别记录的是[i,r](i>=l)的不同gcd值,不同gcd值的数量,有多少种不同的gcd值

Lg[32],Ln[32],Lc同上

所以在合并两个区间ab的时候

我们可以先遍历 a.Rg b.Lg两两进行gcd运算 这一过程将[l,r]中所有中间部分的gcd值进行了更新

然后取出a.Lg[a.Lc-1]也即a整个区间的gcd值,然后遍历b.Lg 进行gcd运算 这一过程相当于将从l开始的gcd值进行了更新

然后取出b.Rg[b.Lc-1]进行如上操作

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn = 10010;
struct node 
{
    LL sum;
    int Lg[32],Rg[32],Ln[32],Rn[32],Lc,Rc;//rg、lg记录的是gcd值 lc、rc记录的是不同gcd值的数量 lnrn记录的是每个gcd值的数量
} tree[maxn<<2];
node pushup(const node &a,const node &b) //合并两个区间
{
    node ret;
    ret.sum=a.sum+b.sum;//之前两个区间自己的先加一下
    for(int i=0;i<a.Rc;++i)
        for(int j=0;j<b.Lc;++j)
            ret.sum+=__gcd(a.Rg[i],b.Lg[j])*1LL*a.Rn[i]*b.Ln[j];//更新函数值
    for(int i=0;i<b.Rc;++i) //更新gcd储值
    {
        ret.Rg[i]=b.Rg[i];
        ret.Rn[i]=b.Rn[i];
    }
    for(int i=0;i<a.Lc;++i) 
    {
        ret.Lg[i]=a.Lg[i];
        ret.Ln[i]=a.Ln[i];
    }
    int d=b.Rg[b.Rc-1],p=b.Rc;//d赋值为b.Rg[b.Rc-1]即为右区间的最小值 也即为右区间的所有数的gcd
    for(int i=0;i<a.Rc;++i) 
    {
        int tmp=__gcd(a.Rg[i],d);
        if(tmp==ret.Rg[p-1]) ret.Rn[p-1]+=a.Rn[i];
        else 
        {
            ret.Rg[p]=tmp;
            ret.Rn[p++]=a.Rn[i];
        }
    }
    ret.Rc=p;
    d = a.Lg[a.Lc-1],p=a.Lc;//同上
    for(int i=0;i<b.Lc;++i) 
    {
        int tmp=__gcd(d,b.Lg[i]);
        if(tmp==ret.Lg[p-1]) ret.Ln[p-1]+=b.Ln[i];
        else 
        {
            ret.Ln[p]=b.Ln[i];
            ret.Lg[p++]=tmp;
        }
    }
    ret.Lc=p;
    return ret;
}
void build(int L,int R,int v)
{
    if(L==R)
    {
        tree[v].Lc=tree[v].Rc=1;
        tree[v].Ln[0]=tree[v].Rn[0]=1;
        scanf("%d",&tree[v].Lg[0]);
        tree[v].sum=tree[v].Rg[0]=tree[v].Lg[0];//L==R时 区间的函数值为读入的数
        return;
    }
    int mid=(L+R)>>1;
    build(L,mid,v<<1);
    build(mid+1,R,v<<1|1);
    tree[v]=pushup(tree[v<<1],tree[v<<1|1]);//pushup的作用就是合并两条线段
}
void query(int L,int R,int lt,int rt,int v)
{
    if(lt<=L&&rt>=R)
    {
        if(lt==L) tree[0]=tree[v];
        else tree[0]=pushup(tree[0],tree[v]);
        return;
    }
    int mid=(L+R)>>1;
    if(lt<=mid) query(L,mid,lt,rt,v<<1);
    if(rt>mid) query(mid+1,R,lt,rt,v<<1|1);
}
int main()
{
    int T,n,m,x,y;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&n);
        build(1,n,1);
        scanf("%d",&m);
        while(m--)
        {
            scanf("%d%d",&x,&y);
            query(1,n,x,y,1);
            printf("%I64d\n",tree[0].sum);
        }
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值