【BZOJ1558】等差数列(JSOI2009)-差分+线段树

测试地址:等差数列
做法:本题需要用到差分+线段树。
我们发现等差数列这个东西非常难维护,于是我们将序列和修改全部差分,我们发现一个有趣的性质:等差数列的差分中各个数相等(其实是句废话)。最关键的是,我们把玄乎的修改操作变成了用三个区间加操作就能完成:从s1s的差分增加了a,从ii+1(si<t)的差分增加了b,从tt+1的差分增加了a(ts)b
有了这样的转化,我们就可以用线段树维护差分序列了,那么看似玄乎的询问和这个差分序列之间有什么关系呢?前面已经说了,等差数列的差分中各个数相等,很多同学就会想,答案是不是就是差分序列中不同数字的段数?然而这个直觉是错的,因为在划分原序列时,我们实际上并不是在将差分序列划分成段,而是把差分序列中某些数删掉,使得剩下的数字中,不同的数字之间不相邻。删掉一个差分序列中的数,就是在原序列中加入一个断点,所以总的段数应该是最小的断点数+1
那我们怎么找到最小的断点数呢?注意到,每有一对相邻的不同数字出现,那么这两个数字中就要至少删掉一个。如果整个差分序列都是长度大于等于2的连续段拼在一起的,那么每个选择间互不影响,最小断点数就是出现这种情况的数对数。现在的问题是,有些连续段长度为1,那么就会有一些选择是相互影响的,于是我们应该把会产生影响的数对合在一起考虑,如果有x个这样的数对连在一起,要满足这些数对的要求需要删掉x+12个数。到了这一步,我们考虑怎么样合并区间的信息,我们要维护区间左端和右端的数(为了判断是不是和另一端接的数字相等),区间左端和右端长度为1的连续段数量(或者和上面一样直接维护数对数,都是为了计算拼起来之后的最优解),整个区间的最优解(即最小断点数)。合并两个区间时,如果左边区间的右端点和右边区间的左端点数字相同,那么两个区间内的断点选择互不影响,直接相加即可。否则,左边区间和右边区间拼起来后,中间产生了新的一段长度为1的连续段,需要重新计算这一段选择的最优解。
于是我们就解决了这一题,时间复杂度为O(nlogn)
以下是本人代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll inf=1000000000ll*1000000000ll;
int n,q,lft[400010],rht[400010],ans[400010],nowr,nowans;
ll v[100010],lftv[400010],rhtv[400010],tag[400010],nowrv;

int Ans(int x)
{
    return (x+1)>>1;
}

void pushdown(int no)
{
    if (tag[no]!=0)
    {
        tag[no<<1]+=tag[no];
        tag[no<<1|1]+=tag[no];
        lftv[no<<1]+=tag[no],rhtv[no<<1]+=tag[no];
        lftv[no<<1|1]+=tag[no],rhtv[no<<1|1]+=tag[no];
        tag[no]=0;
    }
}

void pushup(int no,int l,int r)
{
    int mid=(l+r)>>1;
    if (rhtv[no<<1]!=lftv[no<<1|1])
    {
        ans[no]=ans[no<<1]+ans[no<<1|1];
        ans[no]-=Ans(rht[no<<1])+Ans(lft[no<<1|1]);
        ans[no]+=Ans(rht[no<<1]+lft[no<<1|1]+1);
        if (lft[no<<1]==mid-l)
            lft[no]=lft[no<<1]+lft[no<<1|1]+1;
        else lft[no]=lft[no<<1];
        if (rht[no<<1|1]==r-mid-1)
            rht[no]=rht[no<<1|1]+rht[no<<1]+1;
        else rht[no]=rht[no<<1|1];
    }
    else
    {
        ans[no]=ans[no<<1]+ans[no<<1|1];
        lft[no]=lft[no<<1];
        rht[no]=rht[no<<1|1];
    }
    lftv[no]=lftv[no<<1];
    rhtv[no]=rhtv[no<<1|1];
}

void buildtree(int no,int l,int r)
{
    tag[no]=0;
    if (l>r) return;
    if (l==r)
    {
        lft[no]=rht[no]=ans[no]=0;
        lftv[no]=rhtv[no]=v[l];
        return;
    }
    int mid=(l+r)>>1;
    buildtree(no<<1,l,mid);
    buildtree(no<<1|1,mid+1,r);
    pushup(no,l,r);
}

void add(int no,int l,int r,int s,int t,ll d)
{
    if (s>t||s<1||t>n-1) return;
    if (l>=s&&r<=t)
    {
        tag[no]+=d;
        lftv[no]+=d;
        rhtv[no]+=d;
        return;
    }
    int mid=(l+r)>>1;
    pushdown(no);
    if (s<=mid) add(no<<1,l,mid,s,t,d);
    if (t>mid) add(no<<1|1,mid+1,r,s,t,d);
    pushup(no,l,r);
}

void query(int no,int l,int r,int s,int t)
{
    if (s>t) return;
    if (l>=s&&r<=t)
    {
        if (nowrv!=inf&&nowrv!=lftv[no])
        {
            nowans+=ans[no];
            nowans-=Ans(nowr)+Ans(lft[no]);
            nowans+=Ans(nowr+lft[no]+1);
            if (rht[no]==r-l) nowr=nowr+rht[no]+1;
            else nowr=rht[no];
        }
        else
        {
            nowans+=ans[no];
            nowr=rht[no];
        }
        nowrv=rhtv[no];
        return;
    }
    int mid=(l+r)>>1;
    pushdown(no);
    if (s<=mid) query(no<<1,l,mid,s,t);
    if (t>mid) query(no<<1|1,mid+1,r,s,t);
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%lld",&v[i]);
        v[i-1]=v[i]-v[i-1];
    }

    buildtree(1,1,n-1);
    scanf("%d",&q);
    for(int i=1;i<=q;i++)
    {
        char op[3];
        int s,t;
        ll a,b;
        scanf("%s",&op);
        if (op[0]=='A')
        {
            scanf("%d%d%lld%lld",&s,&t,&a,&b);
            add(1,1,n-1,s-1,s-1,a);
            add(1,1,n-1,s,t-1,b);
            add(1,1,n-1,t,t,-a-(ll)(t-s)*b);
        }
        else
        {
            scanf("%d%d",&s,&t);
            nowr=0;
            nowrv=inf;
            nowans=0;
            query(1,1,n-1,s,t-1);
            printf("%d\n",nowans+1);
        }
    }

    return 0;
}
posted @ 2018-05-23 10:30  Maxwei_wzj  阅读(323)  评论(0编辑  收藏  举报