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