题目大意:
给出n个数,m个操作;
若m==0,表示开炮,开炮后面还有5 个参数l,r,a,k,p表示leijp 向l 到r 段开炮,l 段开了a 炮,以后l + 1 到p 段分别开a+k,a+k+k,
a+k+k+k···炮, p+1 到r 段开a+(p-l-1)k、a+(p-l-2)k···炮
若m==1,后面有l,r 表示询问区域为等差数列的最大长度;
解题思路:
因为升降序改变,单个操作很麻烦,因此不能以单个点建树,而是要以后面的数与前面的数之间的区间作为叶子节点(表示后面的数与前面的数的差),这样的话区间【l,r】更新就只需要增加k;而且求等差数列变成了求最大的连续序列;
因此,要求最大连续数列就要维护lsum,rsum,sum(左右开端的最大连续长度,这个节点的最大连续长度),但仅仅是最大长度并不够,合并的时候虽然它是连续的但并不能保证它的差分相等也就是左右的值相等,因此还需要lc,rc,来表示线段树的左右的值(也就是差分);区间操作lazy操作就不做解释;
得出结论:搞这个题要维护lsum,rsum,sum,l,r,lazy,lc,rc;
代码:
#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
struct node
{
int ls,rs,s;
int lc,rc;
int l,r,lazy;
}tree[100005<<2];
void build(int k,int l,int r)
{
tree[k].l = l,tree[k].r = r;
tree[k].ls = tree[k].rs = tree[k].s = r-l+1;
if(l == r)return;
int mid = (l+r)>>1;
build(k<<1,l,mid);
build(k<<1|1,mid+1,r);
// printf("%d % d %d\n",k,l,r);
}
void pushdown(int k)
{
tree[k<<1].lc += tree[k].lazy;
tree[k<<1].rc += tree[k].lazy;
tree[k<<1].lazy += tree[k].lazy;
tree[k<<1|1].lc += tree[k].lazy;
tree[k<<1|1].rc += tree[k].lazy;
tree[k<<1|1].lazy += tree[k].lazy;
tree[k].lazy = 0;
}
void pushup(int k)
{
tree[k].lc = tree[k<<1].lc;
tree[k].rc = tree[k<<1|1].rc;
tree[k].ls = tree[k<<1].ls;
tree[k].rs = tree[k<<1|1].rs;
if(tree[k<<1].ls == tree[k<<1].r-tree[k<<1].l+1 && tree[k<<1].rc == tree[k<<1|1].lc)
tree[k].ls += tree[k<<1|1].ls;
//同一区间并且相等长度并且左右差分相等
if(tree[k<<1|1].rs == tree[k<<1|1].r-tree[k<<1|1].l+1 && tree[k<<1|1].lc == tree[k<<1].rc)
tree[k].rs += tree[k<<1].rs;
tree[k].s = max(tree[k<<1].s,tree[k<<1|1].s);
if(tree[k<<1].rc == tree[k<<1|1].lc)
tree[k].s = max(tree[k].s,tree[k<<1].rs+tree[k<<1|1].ls);
}
void updata(int k,int l,int r,int d)
{
//printf("%d %d %d %d %d\n",k,tree[k].l,tree[k].r,l,r);
if(tree[k].l == l && tree[k].r == r)
{
tree[k].lc += d;
tree[k].rc += d;
tree[k].lazy += d;
return;
}
if(tree[k].lazy != 0)pushdown(k);
int mid = (tree[k].l+tree[k].r)>>1;
if(l > mid)updata(k<<1|1,l,r,d);
else if(r <= mid)updata(k<<1,l,r,d);
else
{
updata(k<<1,l,mid,d);
updata(k<<1|1,mid+1,r,d);
}
pushup(k);
}
int query(int k,int l,int r,int &lc,int &rc,int &ls,int &rs)
{
if(tree[k].l == l && tree[k].r == r)
{
lc = tree[k].lc;
rc = tree[k].rc;
ls = tree[k].ls;
rs = tree[k].rs;
return tree[k].s;
}
if(tree[k].lazy != 0)pushdown(k);
int mid = (tree[k].l + tree[k].r) >> 1;
if(l > mid)return query(k<<1|1,l,r,lc,rc,ls,rs);
else if(r <= mid)return query(k<<1,l,r,lc,rc,ls,rs);
else
{
int a,b,c,d;
int A,B,C,D;
int tempone = query(k<<1,l,mid,a,b,c,d);
int temptwo = query(k<<1|1,mid+1,r,A,B,C,D);
lc = a,rc = B,ls = c,rs = D;
if(b == A && mid-l+1 == ls)ls += C;
if(b == A && r - mid == rs)rs += d;
int ans = max(tempone,temptwo);
if(b == A)ans = max(ans,d+C);
return ans;
}
pushup(k);
}
int main()
{
freopen("wows.in","r",stdin);
freopen("wows.out","w",stdout);
int n,m,c,l,r,a,d,p,lc=0,rc=0,ls=0,rs=0;
cin>>n>>m;
build(1,1,n-1);//以点与点之间作为叶子节点
for(int i = 1;i <= m;i++)
{
scanf("%d",&c);
if(c == 0)
{
scanf("%d%d%d%d%d",&l,&r,&a,&d,&p);
if(p-1 >= l)updata(1,l,p-1,d);
if(r-1 >= p)updata(1,p,r-1,-d);//更新区间内
if(l != 1)updata(1,l-1,l-1,a);
if(r != n)updata(1,r,r,(l+r-2*p)*d-a); //更新相邻的点
//注意边界
}
if(c == 1)
{
scanf("%d%d",&l,&r);
if(l==r)printf("1\n");
else printf("%d\n",query(1,l,r-1,lc,rc,ls,rs)+1);
}
}
return 0;
}
刷题总结:
这是学长出的题,被坑了许久,不能被惯性思维束缚,仔细分析线段树到底维护什么,不能依照经验去维护点,然而这维护的是点与点之间的区间!!