bzoj 5028: 小Z的加油店 线段树+差分+数论

24 篇文章 0 订阅
4 篇文章 0 订阅

题意

小Z经营一家加油店。小Z加油的方式非常奇怪。他有一排瓶子,每个瓶子有一个容量vi。每次别人来加油,他会让
别人选连续一段的瓶子。他可以用这些瓶子装汽油,但他只有三种操作:
1.把一个瓶子完全加满;
2.把一个瓶子完全倒空;
3.把一个瓶子里的汽油倒进另一个瓶子,直到倒出瓶子空了或者倒进的瓶子满了。
当然,为了回馈用户,小Z会时不时选择连续一段瓶子,给每个瓶子容积都增加x。
为了尽可能给更多的人加油,每次客户来加油他都想知道他能够倒腾出的汽油量最少是多少?
当然他不会一点汽油都不给客户。
1 <= n,m <= 10^5 , 1<=li<=ri<=n , 1<=初始容量,增加的容量<=1000

分析

题目显然就是要我们维护区间加和求区间gcd。
把序列查分一下,就变成了单点修改和区间查询,直接线段树维护即可。

代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>

const int N=100005;

int n,m,a[N];
struct tree{int d,s;}t[N*4];

int gcd(int x,int y)
{
    if (!y) return x;
    else return gcd(y,x%y);
}

void updata(int d)
{
    t[d].s=t[d*2].s+t[d*2+1].s;
    t[d].d=gcd(t[d*2].d,t[d*2+1].d);
}

void build(int d,int l,int r)
{
    if (l==r) {t[d].d=t[d].s=a[l];return;}
    int mid=(l+r)/2;
    build(d*2,l,mid);build(d*2+1,mid+1,r);
    updata(d);
}

void ins(int d,int l,int r,int x,int y)
{
    if (l==r) {t[d].s+=y;t[d].d+=y;return;}
    int mid=(l+r)/2;
    if (x<=mid) ins(d*2,l,mid,x,y);
    else ins(d*2+1,mid+1,r,x,y);
    updata(d);
}

int query(int d,int l,int r,int x,int y)
{
    if (x>y) return 0;
    if (x<=l&&r<=y) return t[d].d;
    int mid=(l+r)/2;
    if (y<=mid) return query(d*2,l,mid,x,y);
    else if (x>mid) return query(d*2+1,mid+1,r,x,y);
    else return gcd(query(d*2,l,mid,x,mid),query(d*2+1,mid+1,r,mid+1,y));
}

int find(int d,int l,int r,int x,int y)
{
    if (x<=l&&r<=y) return t[d].s;
    int mid=(l+r)/2;
    if (y<=mid) return find(d*2,l,mid,x,y);
    else if (x>mid) return find(d*2+1,mid+1,r,x,y);
    else return find(d*2,l,mid,x,mid)+find(d*2+1,mid+1,r,mid+1,y);
}

int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++) scanf("%d",&a[i]);
    for (int i=n;i>1;i--) a[i]-=a[i-1];
    build(1,1,n);
    while (m--)
    {
        int op,l,r;scanf("%d%d%d",&op,&l,&r);
        if (op==1) printf("%d\n",abs(gcd(query(1,1,n,l+1,r),find(1,1,n,1,l))));
        else
        {
            int x;scanf("%d",&x);
            ins(1,1,n,l,x);
            if (r<n) ins(1,1,n,r+1,-x);
        }
    }
    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值