「BZOJ5028」小Z的加油店

发布于 2018-11-24  383 次阅读


题目描述

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

解题报告

首先考虑只有询问怎么做。
一个显然的结论是:能表示出来的最小的数字一定是这些数字的gcd。
所以只有询问的话倍增处理一下gcd就可以了,时间复杂度 $O(nlog^2n)$
然后考虑加入了修改怎么做。
如果只有单点修改还是比较好做的,用线段树维护一下区间gcd,每次修改和查询都是 $O(log^2n)$ 的,总复杂度 $O(nlog^2n)$。
但是这里要求是区间修改,怎么办呢?
我们考虑将这个区间修改变成单点修改可以解决的问题,注意到 $\text{gcd}(a,b) = \text{gcd}(b,a-b)$,所以区间 $[l,r]$ 的 gcd 等价于将这个区间差分后的 gcd。
于是我们用线段树维护一下差分后的数组的区间 gcd 和区间和,支持单点修改和区间查询即可。
时间复杂度 $O(nlog^2n)$

代码

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define Re register
#define LL long long
#define U unsigned
#define FOR(i,a,b) for(Re int i = a;i <= b;++i)
#define ROF(i,a,b) for(Re int i = a;i >= b;--i)
#define SFOR(i,a,b,c) for(Re int i = a;i <= b;i+=c)
#define SROF(i,a,b,c) for(Re int i = a;i >= b;i-=c)
#define CLR(i,a) memset(i,a,sizeof(i))
#define BR printf("--------------------\n")
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl
#define int LL
const int MAXN = 100000+5;

struct SegmentTree *New(int ,int ,SegmentTree *,SegmentTree *);

int _gcd(int a,int b){
    return (!b) ? a : _gcd(b,a%b);
}

struct SegmentTree{
    int l,r,gcd,sum;
    SegmentTree *lc,*rc;
    
    static SegmentTree *build(int l,int r){
        int mid = (l + r) >> 1;
        return (l == r) ? New(l,r,NULL,NULL) : New(l,r,build(l,mid),build(mid+1,r));
    }

    void pushup(){
        gcd = _gcd(lc->gcd,rc->gcd);
        sum = lc->sum + rc->sum;
    }

    void update(int pos,int x){
        if(l == r){
            gcd += x;sum += x;
            return;
        }
        int mid = (l + r) >> 1;
        if(pos <= mid) lc->update(pos,x);
        else rc->update(pos,x);
        pushup();
    }

    int querygcd(int L,int R){
        if(L > R) return 0;
        if(L == l && R == r) return gcd;
        int mid = (l + r) >> 1;
        if(R <= mid) return lc->querygcd(L,R);
        if(L > mid) return rc->querygcd(L,R);
        return _gcd(lc->querygcd(L,mid),rc->querygcd(mid+1,R));
    }

    int querysum(int L,int R){
        //if(L > R) return 0;
        if(L == l && R == r) return sum;
        int mid = (l + r) >> 1;
        if(R <= mid) return lc->querysum(L,R);
        if(L > mid) return rc->querysum(L,R);
        return lc->querysum(L,mid)+rc->querysum(mid+1,R);
    }
}pool[MAXN<<2],*frog = pool,*segt;

SegmentTree *New(int l,int r,SegmentTree *lc,SegmentTree *rc){
    SegmentTree *ret = ++frog;
    ret->l = l;ret->r = r;
    ret->lc = lc;ret->rc = rc;
    ret->gcd = 0;ret->sum = 0;
    return ret;
}

int N,M;
int a[MAXN],c[MAXN];

signed main(){
    scanf("%lld%lld",&N,&M);
    segt = SegmentTree::build(1,N);
    FOR(i,1,N){
        scanf("%lld",a+i);
        segt->update(i,a[i]-a[i-1]);
    }
    while(M--){
        int opt,l,r;scanf("%lld%lld%lld",&opt,&l,&r);
        if(opt == 1) printf("%ld\n",std::labs(_gcd(segt->querysum(1,l),segt->querygcd(l+1,r))));
        else{
            int x;scanf("%lld",&x);
            segt->update(l,x);
            if(r < N) segt->update(r+1,-x);
        }
    }
    return 0;
}
/*
3 4
2 3 4
1 1 3
2 2 2 1
1 1 3
1 2 3   
*/

一个OIer。