BZOJ - 5028 -小Z的加油店(线段树+区间更新+gcd)

题目:BZOJ - 5028

题解:

扩展裴蜀定理+差分+线段树

求从l到r的最小能得到的油量就是求l~r范围内a[i]的gcd

由性质gcd(a,b)=gcd(a,b-a)可得区间gcd可变为:

gcd( a[l], a[l+1], a[l+2],..., a[r] ) = gcd( a[l], a[l+1] - a[l] , a[l+2] - a[l+1] ,..., a[r] - a[r-1] )。

下面谈谈如何证明:

由于 gcd 的性质: 
gcd(a, b) = gcd(a, a-b) 其中 a > b; 
简单证明: 
令 d = gcd(a, b); 
a = d*t1; b = d*t2; 
两式相减:a-b = d*(t1-t2),所以 gcd(a, a-b) = d 

这样对[L, R]区间加, 只要L处加, R+1处减就可以了。 

下面再谈谈为什么只要处理L处加,R+1处减:

gcd(a1, a2 - a1, a3 - a2, a4 - a3, a5 - a4) 
因为当对a1, a2, ….a4(L=1,R=4)处加v时, a1加上了v, 第二项(a2+v) - (a1+v)是不变的,(a3+v) - (a2+v),(a4+v) - (a3+v)也是不变,的,a5 - (a4+v)会少了一个(-v),所以就是说中间的差分是不变的,两头的差分需要更改,那么就要在L处增加一个v,R+1处需要加上一个(-v),这样就实现了L~R的增加操作,因为我求的是差分的gcd,中间的值不会变就不需要更改了

当我们求gcd(l, l + 1, …r)的时候,只要求gcd(al, a(l+1) - al, a(l + 2) - a(l +1) ….ar - a(r - 1)); 
所以我们求gcd[l, r]只需要分布求,先求得gcd[l + 1, r],再求1~l的和,因为1~l的差分和就是al。

(比如说:L=3:a1,a2-a1,a3-a2,那么a3的值就是前1~l的差分和)

所以总的看来,我这个题目只需要维护的就是区间的gcd跟和,然后区间更新L跟R+1位置的差分值,查询1~L的差分和,查询L+1~R的差分gcd,再求这两个的gcd即可

代码:

#include<bits/stdc++.h>
#define N 100005
#define L node<<1
#define R node<<1|1
using namespace std;
int n,m;
int a[N];
struct ljh
{
    int sum,g,l,r;
}e[N<<2];
int gcd(int a,int b)
{
    return (b==0)?a:gcd(b,a%b);
}
void pushup(int node)
{
    e[node].sum=(e[L].sum+e[R].sum);
    e[node].g=gcd(e[L].g,e[R].g);
}
void build(int node,int l,int r)
{
    e[node].l=l;
    e[node].r=r;
    if(l==r)
    {
        e[node].g=e[node].sum=a[l];
        return ;
    }
    int m=(l+r)>>1;
    build(L,l,m);
    build(R,m+1,r);
    pushup(node);
}   
void update(int node,int pos,int k)
{
    if(e[node].l==e[node].r)
    {
        e[node].g+=k;
        e[node].sum+=k;
        return ;
    }
    int m=(e[node].l+e[node].r)>>1;
    if(pos<=m)update(L,pos,k);
    else update(R,pos,k);
    pushup(node);
}
int query_g(int node,int x,int y)
{
    int ans=0;
    if(x<=e[node].l&&e[node].r<=y)
    {
        return e[node].g;
    }
    int m=(e[node].l+e[node].r)>>1;
    if(x<=m)ans=gcd(ans,query_g(L,x,y));
    if(y>m)ans=gcd(ans,query_g(R,x,y));
    return ans;
}
int query_s(int node,int x,int y)
{
    int ans=0;
    if(x<=e[node].l&&e[node].r<=y)
    {
        return e[node].sum;
    }
    int m=(e[node].l+e[node].r)>>1;
    if(x<=m)ans+=query_s(L,x,y);
    if(y>m)ans+=query_s(R,x,y);
    return ans;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    for(int i=n;i>=2;i--)a[i]-=a[i-1];
    build(1,1,n);
    while(m--)
    {
        int op,x,y,z;
        scanf("%d%d%d",&op,&x,&y);
        if(op==1)
        {
            printf("%d\n",gcd(query_g(1,x+1,y),query_s(1,1,x)));
        }
        else
        {
            scanf("%d",&z);
            update(1,x,z);
            if(y<n)update(1,y+1,-z);
        }
    }
}


 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值