SPOJ GSS 1-8的解析——一些跟最大子段和有关的数据结构问题的维护技巧

题目spoj GSS,但是太卡了,无奈之下水洛谷.

T1
题目大意:给定一个长度为 n n n的序列,有 m m m次操作,每次操作查询 [ l , r ] [l,r] [l,r]的最大子段和,不可取空序列.
1 ≤ n ≤ 5 ∗ 1 0 4 1\leq n\leq 5*10^4 1n5104 m m m数据范围为 O ( m log ⁡ n ) O(m\log n) O(mlogn)可过.

一道水题,我们可以得知,区间 [ l , r ] [l,r] [l,r]的最大子段和只有三种可能:左半边最大子段和,右半边最大子段和,左半边一定包括最右端的最大子段和加右半边一定包括最左端的最大子段和.

于是我们线段树每个节点就维护四个信息:最大子段和 s u m sum sum,一定包括最左端最大子段和 l s u m lsum lsum,一定包括最右端最大子段和 r s u m rsum rsum,以及区间和 a n s ans ans.

那么我们就可以写出代码:

#include<bits/stdc++.h>
  using namespace std;
typedef long long LL;
const int N=100000;
struct tree{
  int l,r;
  int sum,lsum,rsum,ans;
}tr[N*5];
int n,m,a[N+1];
void build(int L,int R,int k=1){
  tr[k].l=L;tr[k].r=R;
  if (L==R){
    tr[k].ans=tr[k].sum=tr[k].lsum=tr[k].rsum=a[L];
    return;
  }
  int mid=L+R>>1;
  build(L,mid,k<<1);
  build(mid+1,R,k<<1|1);
  tr[k].ans=tr[k<<1].ans+tr[k<<1|1].ans;
  tr[k].lsum=max(tr[k<<1].ans+tr[k<<1|1].lsum,tr[k<<1].lsum);
  tr[k].rsum=max(tr[k<<1|1].ans+tr[k<<1].rsum,tr[k<<1|1].rsum);
  tr[k].sum=max(max(tr[k<<1].sum,tr[k<<1|1].sum),tr[k<<1].rsum+tr[k<<1|1].lsum);
}
struct tree1{
  int ans,sum,lsum,rsum;
};
tree1 query(int L,int R,int k=1){
  if (L==tr[k].l&&R==tr[k].r) return (tree1){tr[k].ans,tr[k].sum,tr[k].lsum,tr[k].rsum};
  int mid=tr[k].l+tr[k].r>>1;
  if (R<=mid) return query(L,R,k<<1);
  else if (L>mid) return query(L,R,k<<1|1);
    else {
      tree1 u=query(L,mid,k<<1),v=query(mid+1,R,k<<1|1),o;
      o.ans=u.ans+v.ans;
      o.lsum=max(u.ans+v.lsum,u.lsum);
      o.rsum=max(v.ans+u.rsum,v.rsum);
      o.sum=max(max(u.sum,v.sum),u.rsum+v.lsum);
      return o;
    }
}
inline void into(){
  scanf("%d",&n);
  for (int i=1;i<=n;i++)
    scanf("%d",&a[i]);
}
inline void work(){
  build(1,n);
}
inline void outo(){
  scanf("%d",&m);
  int x,y;
  for (int i=1;i<=m;i++){
    scanf("%d%d",&x,&y);
    printf("%d\n",query(x,y).sum);
  }
}
int main(){
  into();
  work();
  outo();
  return 0;
}

T2
与第一题不同的是询问时,若子段中有相同的元素,只能算一个,且可取空序列,其他与GSS1相同.
1 ≤ n , m ≤ 1 0 5 1\leq n,m\leq 10^5 1n,m105.

这道题如果要做的话,因为题目没有修改操作,只有查询操作,所以我们先给询问区间离线搞下来,按照右端点排序.

排序之后,我们可以给它搞几个修改操作,也就是说,一开始整棵树为空.

之后从 i i i 1 1 1枚举到 n n n,每一次都进行一个修改,以及回答右端点为 i i i的询问.

那么我们定义一棵线段树,假设当前扫到了第 i i i个点,那么其中第 j j j个叶子节点表示的就是区间 [ j , i ] [j,i] [j,i].

那么我们线段树的非叶子节点其实就没有用了,可以什么信息也不存,只存标记.

我们现在用 m a ma ma表示区间 [ j , i ] [j,i] [j,i]中必须包括右端点 i i i的最大子段和,由于我们不一定要取右端点,所以我们再添加一个 h m a hma hma表示历史最大值(即答案).

那么我们考虑add操作,由于我们要让最大子段和之中相等的数只算一个,那我们设与第 i i i个数相等的数在最后面的位置是 l a s t last last,则我们要修改的区间就是 [ l a s t + 1 , i ] [last+1,i] [last+1,i],所以我们要学会打标记.

所以我们再加两个元素 t a g tag tag h t a g htag htag,分别表示增量总和和历史最大增量,其中历史最大增量就是指增量出现过的最大值.

之后在找到了需要增加的区间的时候,我们可以让 t a g tag tag先加上这个数,然后 h t a g htag htag取当前 t a g tag tag h t a g htag htag的最大值.

若这个区间是叶子节点,我们则需要将 m a ma ma加上 n u m num num,且 h m a hma hma m a ma ma h m a hma hma的最大值.

当下传懒标记也就是pushdown的时候,我们要将 k k k的儿子的 t a g tag tag都加上 t r [ k ] . t a g tr[k].tag tr[k].tag k k k的儿子的 m a ma ma加上 t r [ k ] . t a g tr[k].tag tr[k].tag.

但是当时历史最大时,我们发现这个标记节点 k k k的祖先肯定没有任何标记了,也就是说 t r [ k ] . h t a g tr[k].htag tr[k].htag一定是一段连续的,而且显然的, t r [ k . s o n ] . m a tr[k.son].ma tr[k.son].ma t r [ k . s o n ] . t a g tr[k.son].tag tr[k.son].tag肯定也是连续的一段,并且这一段肯定是紧贴 t r [ k ] . h t a g tr[k].htag tr[k].htag的,所以我们可以得出结论:
t r [ k . s o n ] . h m a = m a x ( t r [ k . s o n ] . h m a , t r [ k . s o n ] . m a + t r [ k ] . h t a g ) t r [ k . s o n ] . h t a g = m a x ( t r [ k . s o n ] . h t a g , t r [ k . s o n ] . t a g + t r [ k ] . h t a g ) tr[k.son].hma=max(tr[k.son].hma,tr[k.son].ma+tr[k].htag)\\ tr[k.son].htag=max(tr[k.son].htag,tr[k.son].tag+tr[k].htag) tr[k.son].hma=max(tr[k.son].hma,tr[k.son].ma+tr[k].htag)tr[k.son].htag=max(tr[k.son].htag,tr[k.son].tag+tr[k].htag)

注意 h m a hma hma一定要放在 m a ma ma前更新, h t a g htag htag一定要放在 t a g tag tag前更新.

代码如下:

#include<bits/stdc++.h>
  using namespace std;
typedef long long LL;
const int N=500000;
struct tree{
  int l,r;
  LL ma,hma,tag,htag;
}tr[N*5];
struct question{
  int l,r,id;
}q[N+1];
int n,m,now[N+1],last[N+1],lx[N+1];
LL a[N+1],ans[N+1];
inline void into(){
  scanf("%d",&n);
  for (int i=1;i<=n;i++)
    scanf("%lld",&a[i]);
  scanf("%d",&m);
  for (int i=1;i<=m;i++)
    scanf("%d%d",&q[i].l,&q[i].r),q[i].id=i;
}
bool cmp(question a,question b){
  return a.r<b.r;
}
void build(int L,int R,int k=1){
  tr[k].l=L;tr[k].r=R;
  if (L==R) return;
  int mid=L+R>>1;
  build(L,mid,k<<1);build(mid+1,R,k<<1|1);
}
void pushdown(int k){
  int ls=k<<1,rs=ls|1;
  tr[ls].htag=max(tr[ls].htag,tr[ls].tag+tr[k].htag);
  tr[rs].htag=max(tr[rs].htag,tr[rs].tag+tr[k].htag);
  tr[ls].hma=max(tr[ls].hma,tr[ls].ma+tr[k].htag);
  tr[rs].hma=max(tr[rs].hma,tr[rs].ma+tr[k].htag);
  tr[ls].tag+=tr[k].tag;tr[rs].tag+=tr[k].tag;
  tr[ls].ma+=tr[k].tag;tr[rs].ma+=tr[k].tag;
  tr[k].tag=tr[k].htag=0LL;
}
void pushup(int k){
  tr[k].ma=max(tr[k<<1].ma,tr[k<<1|1].ma);
  tr[k].hma=max(tr[k<<1].hma,tr[k<<1|1].hma);
}
void add(int L,int R,LL num,int k=1){
  if (tr[k].l==L&&tr[k].r==R){
    tr[k].ma+=num;
    tr[k].tag+=num;
    tr[k].htag=max(tr[k].htag,tr[k].tag);
    tr[k].hma=max(tr[k].hma,tr[k].ma);
    return;
  }
  pushdown(k);
  int mid=tr[k].l+tr[k].r>>1;
  if (R<=mid) add(L,R,num,k<<1);
  else if (L>mid) add(L,R,num,k<<1|1);
    else add(L,mid,num,k<<1),add(mid+1,R,num,k<<1|1);
  pushup(k);
}
LL query(int L,int R,int k=1){
  if (tr[k].l==L&&tr[k].r==R) return tr[k].hma;
  pushdown(k);
  int mid=tr[k].l+tr[k].r>>1;
  if (R<=mid) return query(L,R,k<<1);
  else if (L>mid) return query(L,R,k<<1|1);
    else return max(query(L,mid,k<<1),query(mid+1,R,k<<1|1));
}
inline void work(){
  for (int i=1;i<=n;i++)
    last[i]=lx[a[i]+100000],lx[a[i]+100000]=i;
  sort(q+1,q+1+m,cmp);
  build(1,n);
  int j=1;
  for (int i=1;i<=n;i++){
    add(last[i]+1,i,a[i]);
    for (;j<=m&&q[j].r==i;j++) ans[q[j].id]=query(q[j].l,q[j].r);
  }
}
inline void outo(){
  for (int i=1;i<=m;i++)
    printf("%lld\n",ans[i]);
}
int main(){
  into();
  work();
  outo();
  return 0;
}

T3
在T1的基础上,增加单点修改的操作.
1 ≤ n , m ≤ 5 ∗ 1 0 4 1\leq n,m\leq 5*10^4 1n,m5104.

这其实跟T1的思路是一样的,多写一个修改函数就可以了.

代码如下:

#include<bits/stdc++.h>
  using namespace std;
#define Abigail inline void
typedef long long LL;
const int N=50000;
struct tree{
  int l,r,sum,ans,lans,rans;
}tr[N*5];
int n,q,a[N+5];
void pushup(int k){
  int ls=k<<1,rs=k<<1|1;
  tr[k].sum=tr[ls].sum+tr[rs].sum;
  tr[k].lans=max(tr[ls].lans,tr[ls].sum+tr[rs].lans);
  tr[k].rans=max(tr[rs].rans,tr[rs].sum+tr[ls].rans);
  tr[k].ans=max(max(tr[ls].ans,tr[rs].ans),tr[ls].rans+tr[rs].lans);
}
void build(int L,int R,int k=1){
  tr[k].l=L;tr[k].r=R;
  if (L==R){
    tr[k].ans=tr[k].lans=tr[k].rans=tr[k].sum=a[L];
    return;
  }
  int mid=L+R>>1;
  build(L,mid,k<<1);build(mid+1,R,k<<1|1);
  pushup(k);
}
void change(int x,int num,int k=1){
  if (tr[k].l==tr[k].r){
    tr[k].sum=tr[k].ans=tr[k].lans=tr[k].rans=num;
    return;
  }
  int mid=tr[k].l+tr[k].r>>1;
  if (x<=mid) change(x,num,k<<1);
  else change(x,num,k<<1|1);
  pushup(k);
}
struct tree1{
  int sum,ans,lans,rans;
};
tree1 query(int L,int R,int k=1){
  if (tr[k].l==L&&tr[k].r==R) return (tree1){tr[k].sum,tr[k].ans,tr[k].lans,tr[k].rans};
  int mid=tr[k].l+tr[k].r>>1;
  if (R<=mid) return query(L,R,k<<1);
  else if (L>mid) return query(L,R,k<<1|1);
    else {
      tree1 u=query(L,mid,k<<1),v=query(mid+1,R,k<<1|1),o;
      o.sum=u.sum+v.sum;
      o.lans=max(u.lans,u.sum+v.lans);
      o.rans=max(v.rans,v.sum+u.rans);
      o.ans=max(max(u.ans,v.ans),u.rans+v.lans);
      return o;
    }
}
Abigail into(){
  scanf("%d",&n);
  for (int i=1;i<=n;i++)
    scanf("%d",&a[i]);
  build(1,n);
  scanf("%d",&q);
}
Abigail work(){
  int opt,x,y;
  for (int i=1;i<=q;i++){
    scanf("%d%d%d",&opt,&x,&y);
    if (opt) printf("%d\n",query(x,y).ans);
    else change(x,y);
  }
}
Abigail outo(){
}
int main(){
  into();
  work();
  outo();
  return 0;
}

T4
对于一个长度为 n n n的数列,支持下列操作:
1.格式 0 &ThinSpace; l &ThinSpace; r 0\,l\,r 0lr,表示区间开方.
2.格式 1 &ThinSpace; l &ThinSpace; r 1\,l\,r 1lr,表示查询区间和.
设操作次数为 m m m,则 1 ≤ n , m ≤ 1 0 5 1\leq n,m\leq 10^5 1n,m105.

这道题看起来十分不可做,因为区间开方我们无法支持区间的整体修改.

但是我们可以发现,开方有个很好的性质,就是开方到最后都会变成 0 0 0或者 1 1 1,然后就不会变了.

所以我们可以利用这个性质,每个区间记录是否已经全部为 0 0 0 1 1 1,若不是,就暴力修改.

那么我们可以证明,因为开方一次数的长度就会变成原来的一半,所以区间开方的操作的使用次数不会很多,不用担心会TLE.

我们可以计算一下总共的复杂度,最多会给每一个点进行 5 5 5次开方就会为 0 0 0 1 1 1,所以开方次数可以看做常数,一次操作也可以近似地看成 O ( log ⁡ n ) O(\log n) O(logn)的.

事实上如果真的要算的话,一次操作的时间复杂度是均摊 O ( log ⁡ n log ⁡ log ⁡ n ) O(\log n\log\log n) O(lognloglogn)的.

代码如下:

#include<bits/stdc++.h>
  using namespace std;
#define Abigail inline void
typedef long long LL;
const int N=100000;
struct tree{
  int l,r;
  bool flag;
  LL sum;
}tr[N*5];
int n,q,T;
LL a[N+5];
void pushup(int k){
  tr[k].sum=tr[k<<1].sum+tr[k<<1|1].sum;
  if (tr[k<<1].flag&&tr[k<<1|1].flag) tr[k].flag=1;
}
void build(int L,int R,int k=1){
  tr[k].l=L;tr[k].r=R;
  tr[k].flag=0;
  if (L==R) {
    tr[k].sum=a[L];
    if (tr[k].sum==1LL||tr[k].sum==0LL) tr[k].flag=1;
    return;
  }
  int mid=L+R>>1;
  build(L,mid,k<<1);build(mid+1,R,k<<1|1);
  pushup(k);
}
void change(int L,int R,int k=1){
  if (tr[k].flag) return;
  if (tr[k].l==tr[k].r) {
    tr[k].sum=floor(sqrt(tr[k].sum));
    if (tr[k].sum==1LL||tr[k].sum==0LL) tr[k].flag=1;
    return;
  }
  int mid=tr[k].l+tr[k].r>>1;
  if (R<=mid) change(L,R,k<<1);
  else if (L>mid) change(L,R,k<<1|1);
    else change(L,mid,k<<1),change(mid+1,R,k<<1|1);
  pushup(k);
}
LL query(int L,int R,int k=1){
  if (L==tr[k].l&&R==tr[k].r) return tr[k].sum;
  int mid=tr[k].l+tr[k].r>>1;
  if (R<=mid) return query(L,R,k<<1);
  else if (L>mid) return query(L,R,k<<1|1);
    else return query(L,mid,k<<1)+query(mid+1,R,k<<1|1);
}
Abigail into(){
  for (int i=1;i<=n;i++)
    scanf("%lld",&a[i]);
  build(1,n);
  scanf("%d",&q);
}
Abigail work(){
  printf("Case #%d:\n",++T);
  int opt,x,y;
  for (int i=1;i<=q;i++){
    scanf("%d%d%d",&opt,&x,&y);
    if (x>y) swap(x,y);
    if (opt) printf("%lld\n",query(x,y));
    else change(x,y);
  }
  printf("\n");
}
Abigail outo(){
}
int main(){
  while (~scanf("%d",&n)){
    into();
    work();
    outo();
  }
  return 0;
}

T5
题目大意:给定一串长度为 n n n的数列,维护一个数据结构支持查询左端点在 [ l 1 , r 1 ] [l1,r1] [l1,r1],右端点在 [ l 2 , r 2 ] [l2,r2] [l2,r2]中的最大区间和.
设操作次数为 m m m,则 1 ≤ n , m ≤ 1 0 4 1\leq n,m\leq 10^4 1n,m104.

这道题准备好分类讨论了.

首先线段树还是那么一棵,但是我们得把这些询问给分几种情况讨论:

l 1 = l 2 , r 1 = r 2 l1=l2,r1=r2 l1=l2,r1=r2时,直接计算 [ l 1 , r 1 ] [l1,r1] [l1,r1]的最大子段和.

l 1 &lt; = r 1 &lt; l 2 &lt; = r 2 l1&lt;=r1&lt;l2&lt;=r2 l1<=r1<l2<=r2时,那么将这个询问分成三部分,区间 [ l 1 , r 1 ] [l1,r1] [l1,r1]必须包括右端点的最大子段和+区间 [ r 1 + 1 , l 2 − 1 ] [r1+1,l2-1] [r1+1,l21]的和+区间 [ l 2 , r 2 ] [l2,r2] [l2,r2]必须包括左端点的最大子段和.

l 1 &lt; = l 2 &lt; = r 1 &lt; = r 2 l1&lt;=l2&lt;=r1&lt;=r2 l1<=l2<=r1<=r2时,这种情况询问要分成几种情况:
1.左端点在 [ l 2 , r 1 ] [l2,r1] [l2,r1],右端点也在 [ l 2 , r 1 ] [l2,r1] [l2,r1],那么是区间 [ l 2 , r 1 ] [l2,r1] [l2,r1]的最大子段和.
2.左端点在 [ l 1 , l 2 − 1 ] [l1,l2-1] [l1,l21],右端点在 [ l 2 , r 2 ] [l2,r2] [l2,r2],那么是区间 [ l 1 , l 2 − 1 ] [l1,l2-1] [l1,l21]的必须包括右端点的最大子段和+区间 [ l 2 , r 2 ] [l2,r2] [l2,r2]必须包括左端点的最大子段和.
3.右端点在 [ r 1 + 1 , r 2 ] [r1+1,r2] [r1+1,r2],左端点在 [ l 1 , r 1 ] [l1,r1] [l1,r1],那么是区间 [ r 1 + 1 , r 2 ] [r1+1,r2] [r1+1,r2]必须包括左端点的最大子段和+区间 [ l 1 , r 1 ] [l1,r1] [l1,r1]必须包括右端点的最大子段和.

那么代码就好写了,不过细节较多:

#include<bits/stdc++.h>
  using namespace std;
#define Abigail inline void
#define rep(i,j,k) for (int i=j;i<=k;i++)
typedef long long LL;
const int N=100000;
const int INF=(1<<30)-1+(1<<30);
struct tree{
  int l,r,sum,lsum,rsum,ans;
}tr[N*5];
int n,m,a[N+9];
void build(int L,int R,int k=1){
  tr[k].l=L;tr[k].r=R;
  if(L==R){
    tr[k].sum=tr[k].lsum=tr[k].rsum=tr[k].ans=a[L];
    return;
  }
  int mid=L+R>>1;
  build(L,mid,k<<1);build(mid+1,R,k<<1|1);
  int ls=k<<1,rs=k<<1|1;
  tr[k].ans=tr[ls].ans+tr[rs].ans;
  tr[k].lsum=max(tr[ls].lsum,tr[ls].ans+tr[rs].lsum);
  tr[k].rsum=max(tr[rs].rsum,tr[rs].ans+tr[ls].rsum);
  tr[k].sum=max(max(tr[ls].sum,tr[rs].sum),tr[ls].rsum+tr[rs].lsum);
}
struct tree1{
  int sum,lsum,rsum,ans;
};
tree1 query(int L,int R,int k=1){
  if (R<L) return (tree1){0,0,0,0};
  if (L==tr[k].l&&R==tr[k].r) return (tree1){tr[k].sum,tr[k].lsum,tr[k].rsum,tr[k].ans};
  int mid=tr[k].l+tr[k].r>>1;
  if (R<=mid) return query(L,R,k<<1);
  else if (L>mid) return query(L,R,k<<1|1);
    else {
      tree1 u=query(L,mid,k<<1),v=query(mid+1,R,k<<1|1),o;
      o.ans=u.ans+v.ans;
      o.lsum=max(u.lsum,u.ans+v.lsum);
      o.rsum=max(v.rsum,v.ans+u.rsum);
      o.sum=max(max(u.sum,v.sum),u.rsum+v.lsum);
      return o;
    }
}
Abigail into(){
  memset(tr,0,sizeof(tr));
  scanf("%d",&n);
  for (int i=1;i<=n;i++) scanf("%d",&a[i]);
  scanf("%d",&m);
}
Abigail work(){
  build(1,n);
  int l1,r1,l2,r2;
  for (int i=1;i<=m;i++){
    scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
    if (l1==l2&&r1==r2) printf("%d\n",query(l1,r1).sum);
    else if (r1<l2) printf("%d\n",query(l1,r1).rsum+query(r1+1,l2-1).ans+query(l2,r2).lsum);
      else{
        int o1=query(l2,r1).sum,o2=query(l1,l2-1).rsum+query(l2,r2).lsum,o3=query(r1+1,r2).lsum+query(l1,r1).rsum;
        printf("%d\n",max(max(o1,o2),o3));
      }
  }
}
Abigail outo(){
}
int main(){
  int T=0;
  scanf("%d",&T);
  while (T--){
    into();
    work();
    outo();
  }
  return 0;
}

T6
题目大意:维护一个数据结构支持在一个长度为 n n n的序列上:
1.格式 I &ThinSpace; x &ThinSpace; y I\,x\,y Ixy,在第 x x x个元素前插入一个元素 y y y.
2.格式 D &ThinSpace; x D\,x Dx,删除第 x x x个元素.
3. 格式 R &ThinSpace; x &ThinSpace; y R\,x\,y Rxy,修改第 x x x个元素的值变为 y y y.
4.格式 Q &ThinSpace; l &ThinSpace; r Q\,l\,r Qlr,查询区间 [ l , r ] [l,r] [l,r]的最大子段和.
设操作次数为 m m m,则 1 ≤ n , m ≤ 1 0 5 1\leq n,m\leq 10^5 1n,m105.

由于需要支持插入删除的原因,所以我们必须使用平衡树来维护此题.

然而这道题又要维护区间信息,这告诉我们应该使用splay这种数据结构.

其实这道题到这里的时候已经很裸了.

代码如下:

#include<bits/stdc++.h>
  using namespace std;
#define Abigail inline void
#define rep(i,j,k) for (int i=j;i<=k;i++)
typedef long long LL;
const int N=100000;
const int INF=(1<<30)-1+(1<<30);
struct tree{
  int dad,s[2],size,lans,rans,ans,sum,x;
}tr[N*2+9];
int top,n,q,a[N+9],root;
void pushup(int k){
  int ls=tr[k].s[0],rs=tr[k].s[1];
  tr[k].size=tr[ls].size+tr[rs].size+1;
  tr[k].sum=tr[ls].sum+tr[rs].sum+tr[k].x;
  if (ls&&rs){
    tr[k].lans=max(tr[ls].lans,tr[ls].sum+tr[k].x+max(0,tr[rs].lans));
    tr[k].rans=max(tr[rs].rans,tr[rs].sum+tr[k].x+max(0,tr[ls].rans));
    tr[k].ans=max(max(tr[ls].ans,tr[rs].ans),tr[k].x+max(tr[ls].rans,0)+max(tr[rs].lans,0));
  }else if (!ls&&rs){
    tr[k].lans=tr[k].x+max(tr[rs].lans,0);
    tr[k].rans=max(tr[rs].rans,tr[rs].sum+tr[k].x);
    tr[k].ans=max(tr[rs].ans,tr[k].x+max(tr[rs].lans,0));
  }else if (ls&&!rs){
    tr[k].lans=max(tr[ls].lans,tr[ls].sum+tr[k].x);
    tr[k].rans=tr[k].x+max(tr[ls].rans,0);
    tr[k].ans=max(tr[ls].ans,tr[k].x+max(tr[ls].rans,0));
  }else tr[k].lans=tr[k].rans=tr[k].ans=tr[k].x;
}
void rotate(int k){
  int x=tr[k].dad,y=tr[x].dad,b=tr[x].s[1]==k;
  tr[y].s[tr[y].s[1]==x]=k;tr[k].dad=y;
  tr[x].s[b]=tr[k].s[b^1];tr[tr[k].s[b^1]].dad=x;
  tr[k].s[b^1]=x;tr[x].dad=k;
  pushup(x);pushup(k);
}
void splay(int x,int go){
  int y=tr[x].dad,z=tr[y].dad;
  for (;tr[x].dad^go;rotate(x)){
    y=tr[x].dad,z=tr[y].dad;
    if (z^go) (tr[y].s[0]==x)^(tr[z].s[0]==y)?rotate(x):rotate(y);
  }
  if (!go) root=x;
}
void build(int L,int R){
  int mid=L+R>>1,u=++top;
  tr[top].x=a[mid];
  if (L<=mid-1){
    tr[top+1].dad=u;tr[u].s[0]=top+1;
    build(L,mid-1);
  } 
  if(mid+1<=R){
    tr[top+1].dad=u;tr[u].s[1]=top+1;
    build(mid+1,R);
  }
  pushup(u);
}
int find(int x){
  int u=root;
  while (1)
    if (x<=tr[tr[u].s[0]].size) u=tr[u].s[0];
    else if (x>tr[tr[u].s[0]].size+1) x-=tr[tr[u].s[0]].size+1,u=tr[u].s[1];
      else break;
  splay(u,0);
  return u;
}
void insert(int x,int y){
  int l=find(x-1),r=find(x);
  splay(l,0);splay(r,l);
  tr[r].s[0]=++top;tr[top].dad=r;tr[top].x=y;
  pushup(top);pushup(r);pushup(l);
}
void erase(int x){
  int l=find(x-1),r=find(x+1);
  splay(l,0);splay(r,l);
  tr[r].s[0]=0;
  pushup(r);pushup(l);
}
void change(int x,int y){
  int u=find(x);
  tr[u].x=y;
  pushup(u);
}
int query(int x,int y){
  int l=find(x-1),r=find(y+1);
  splay(l,0);splay(r,l);
  return tr[tr[r].s[0]].ans;
}
char rc(){
  char c=getchar();
  for (;c<'A'||c>'Z';c=getchar());
  return c;
}
int ri(){
  int x=0,y=1;
  char c=getchar();
  for (;c<'0'||c>'9';c=getchar()) if (c=='-') y=-1;
  for (;c<='9'&&c>='0';c=getchar()) x=x*10+c-'0';
  return x*y;
}
Abigail into(){
  n=ri();
  for (int i=1;i<=n;i++)
    a[i]=ri(); 
  q=ri();
}
Abigail work(){
  build(0,n+1);
  root=1;
  int x,y;
  char c;
  for (int i=1;i<=q;i++){
    c=rc();
    switch (c){
      case 'I':x=ri();y=ri();
               insert(x+1,y);
               break;
      case 'D':x=ri();
               erase(x+1);
               break;
      case 'R':x=ri();y=ri();
               change(x+1,y);
               break;
      case 'Q':x=ri();y=ri();
               printf("%d\n",query(x+1,y+1));
               break;
    }
  }
}
Abigail outo(){
}
int main(){
  into();
  work();
  outo();
  return 0;
}

T7
题目大意:维护一棵节点数为 n n n的树,支持:
1.格式 1 &ThinSpace; a &ThinSpace; b 1\,a\,b 1ab,表示查询点 a a a到点 b b b这条链上的最大子段和.
2.格式 2 &ThinSpace; a &ThinSpace; b &ThinSpace; c 2\,a\,b\,c 2abc,表示将点 a a a到点 b b b这条链上的所有点权修改为 c c c.
设操作次数为 m m m,则 1 ≤ n , m ≤ 1 0 5 1\leq n,m\leq 10^5 1n,m105.

注意我们可以最大子段和一个元素都不取.

这道题明显就是树链剖分的题,整道题也没有什么难度,大体上就是树链剖分的入门题.

不过注意查询的最大子段和的时候,链与链之间的合并这个东西,我们需要注意,这个东西的顺序是不能改变的,也就是说我们不能交换 u u u v v v这样去做了.

这个时候的合并顺序就显得十分的难确定了,但是我们可以先确定 u → l c a ( u , v ) u\rightarrow lca(u,v) ulca(u,v)这条链一定在 v → l c a ( u , v ) v\rightarrow lca(u,v) vlca(u,v)这条链左边,得到两条链,然后再合并,这样就会变得较为简单.

我们可以结合下面的图想象一下该怎么结合这个东西:
在这里插入图片描述
代码如下:

#include<bits/stdc++.h>
  using namespace std;
#define Abigail inline void
#define rep(i,j,k) for (int i=j;i<=k;i++)
typedef long long LL;
const int N=100000;
const int INF=(1<<30)-1+(1<<30);
struct side{
  int y,next;
}e[N*2+9];
struct node{
  int deep,dad,son,size,dfn,top,v;
}nod[N+9];
struct tree{
  int l,r,ans,lans,rans,sum,tag;
  bool flag;      //如果你不加一个flag你会误判一个区间修改权值成0的情况,能活生生把你卡成WA卡一个下午
}tr[N*5];
int n,q,a[N+9],order[N+9],t,top,lin[N+9];
void ins(int X,int Y){
  e[++top].y=Y;
  e[top].next=lin[X];
  lin[X]=top;
}
void dfs1(int k,int fa){
  nod[k].dad=fa;
  nod[k].deep=nod[fa].deep+1;
  nod[k].size=1;
  for (int i=lin[k];i;i=e[i].next)
    if (e[i].y^fa){
      dfs1(e[i].y,k);
      nod[k].size+=nod[e[i].y].size;
      if (nod[nod[k].son].size<nod[e[i].y].size) nod[k].son=e[i].y;
    }
}
void dfs2(int k,int start){
  nod[k].top=start;
  nod[k].dfn=++t;
  order[t]=k;
  if (nod[k].son) dfs2(nod[k].son,start);
  for (int i=lin[k];i;i=e[i].next)
    if (e[i].y^nod[k].dad&&e[i].y^nod[k].son) dfs2(e[i].y,e[i].y);
}
void update(int k,int num){
  tr[k].flag=1;
  tr[k].tag=num;
  tr[k].sum=num*(tr[k].r-tr[k].l+1);
  tr[k].lans=tr[k].rans=tr[k].ans=max(0,num*(tr[k].r-tr[k].l+1));
}
void pushup(int k){
  int ls=k<<1,rs=k<<1|1;
  tr[k].sum=tr[ls].sum+tr[rs].sum;
  tr[k].lans=max(tr[ls].lans,tr[ls].sum+tr[rs].lans);
  tr[k].rans=max(tr[rs].rans,tr[rs].sum+tr[ls].rans);
  tr[k].ans=max(max(tr[ls].ans,tr[rs].ans),tr[ls].rans+tr[rs].lans);
}
void pushdown(int k){
  if (!tr[k].flag) return;
  update(k<<1,tr[k].tag);update(k<<1|1,tr[k].tag);
  tr[k].tag=tr[k].flag=0;
}
void build(int L,int R,int k=1){
  tr[k].l=L;tr[k].r=R;
  if (L==R){
    tr[k].lans=tr[k].rans=tr[k].ans=max(tr[k].sum=nod[order[L]].v,0);
    return;
  }
  int mid=L+R>>1;
  build(L,mid,k<<1),build(mid+1,R,k<<1|1);
  pushup(k);
}
void change(int L,int R,int num,int k=1){
  if (L==tr[k].l&&R==tr[k].r){
    update(k,num);
    return;
  }
  pushdown(k);
  int mid=tr[k].l+tr[k].r>>1;
  if (R<=mid) change(L,R,num,k<<1);
  else if (L>mid) change(L,R,num,k<<1|1);
    else change(L,mid,num,k<<1),change(mid+1,R,num,k<<1|1);
  pushup(k);
}
struct tree1{
  int lans,rans,ans,sum;
};
tree1 merge(tree1 u,tree1 v){
  tree1 o;
  o.sum=u.sum+v.sum;
  o.lans=max(u.lans,u.sum+v.lans);
  o.rans=max(v.rans,v.sum+u.rans);
  o.ans=max(max(u.ans,v.ans),u.rans+v.lans);
  return o;
}
tree1 rez(tree1 o){
  swap(o.lans,o.rans);
  return o;
}
tree1 query(int L,int R,int k=1){
  if (L==tr[k].l&&R==tr[k].r) return (tree1){tr[k].lans,tr[k].rans,tr[k].ans,tr[k].sum};
  pushdown(k);
  int mid=tr[k].l+tr[k].r>>1;
  if (R<=mid) return query(L,R,k<<1);
  else if (L>mid) return query(L,R,k<<1|1);
    else return merge(query(L,mid,k<<1),query(mid+1,R,k<<1|1));
}
void Change(int u,int v,int num){
  while (nod[u].top^nod[v].top){
    if (nod[nod[u].top].deep<nod[nod[v].top].deep) swap(u,v);
    change(nod[nod[u].top].dfn,nod[u].dfn,num);
    u=nod[nod[u].top].dad;
  }
  if (nod[u].deep>nod[v].deep) swap(u,v);
  change(nod[u].dfn,nod[v].dfn,num);
}
int Query(int u,int v){      //这个查询好好自己理解一下,对照上面的那个图想象一下
  tree1 fu,fv;
  fu.ans=fu.lans=fu.rans=fu.sum=fv.ans=fv.lans=fv.rans=fv.sum=0;
  while (nod[u].top^nod[v].top)
    if (nod[nod[u].top].deep>nod[nod[v].top].deep){
      fu=merge(fu,rez(query(nod[nod[u].top].dfn,nod[u].dfn)));
      u=nod[nod[u].top].dad;
    }else{
      fv=merge(query(nod[nod[v].top].dfn,nod[v].dfn),fv);
      v=nod[nod[v].top].dad;
    }
  if (nod[u].deep>nod[v].deep) return merge(merge(fu,rez(query(nod[v].dfn,nod[u].dfn))),fv).ans;
  else return merge(fu,merge(query(nod[u].dfn,nod[v].dfn),fv)).ans;
}
Abigail into(){
  scanf("%d",&n);
  for (int i=1;i<=n;i++)
    scanf("%d",&nod[i].v);
  int x,y;
  for (int i=1;i<n;i++){
    scanf("%d%d",&x,&y);
    ins(x,y);ins(y,x);
  }
}
Abigail work(){
  scanf("%d",&q);
  dfs1(1,0);
  dfs2(1,1);
  build(1,n);
  int a,b,c,opt;
  for (int i=1;i<=q;i++){
    scanf("%d",&opt);
    if (opt==1){
      scanf("%d%d",&a,&b);
      printf("%d\n",Query(a,b));
    }else{
      scanf("%d%d%d",&a,&b,&c);
      Change(a,b,c);
    }
  }
}
Abigail outo(){
}
int main(){
  into();
  work();
  outo();
  return 0;
}

T8
题目大意:维护一个序列 A [ 0.. n − 1 ] A[0..n-1] A[0..n1],支持:
1.格式 I &ThinSpace; x &ThinSpace; y I\,x\,y Ixy,表示在第 x x x个元素前插入一个元素 y y y.
2.格式 D &ThinSpace; x D\,x Dx,表示删除第 x x x个元素.
3.格式 R &ThinSpace; x &ThinSpace; y R\,x\,y Rxy,表示将第 x x x个元修改为 y y y.
4.格式 Q &ThinSpace; l &ThinSpace; r &ThinSpace; k Q\,l\,r\,k Qlrk,表示查询 ∑ i = l r A [ i ] ∗ ( i − l + 1 ) k &ThinSpace;&ThinSpace; m o d &ThinSpace;&ThinSpace; 2 32 \sum_{i=l}^{r}A[i]*(i-l+1)^{k}\,\,mod\,\,2^{32} i=lrA[i](il+1)kmod232的值.
1 ≤ n , q ≤ 1 0 5 1\leq n,q\leq 10^5 1n,q105.

这题一看就是一道splay的题,但是难点在于操作4如何处理询问.

其实这道题如果没有 k k k次方的影响会容易很多,但是 k k k次方的话会让很多优美的性质被破坏掉.

这道题我们先考虑 k = 1 k=1 k=1时的情况,我们可以直接考虑两个子区间如何合并成一个区间,我们可以从左右两个子区间的答案和+左区间长度*右区间的和来得到这个区间的答案.

但是当 k k k不是 1 1 1的时候,我们就需要考虑右半边的答案不能直接单纯的拆分成左区间长度的 k k k次方*右区间所有数之和+右区间答案了,这样会产生一些问题.

但是我们的思路还是一样的,要将右半边的答案算出来与左半边相加.

这个时候我们考虑用一个 a n s [ i ] ans[i] ans[i]储存一个区间 k = i k=i k=i时的答案.

那么接下来我们就开始看,我们假设整个区间是 [ l , r ] [l,r] [l,r],区间中点是 m i d mid mid,那么我们该如何得到右半边的答案:
∑ i = m i d + 1 r A [ i ] ( i − l + 1 ) k = ∑ i = m i d + 1 r A [ i ] ( ( m i d − l + 1 ) + ( i − m i d ) ) k \sum_{i=mid+1}^{r}A[i](i-l+1)^k\\ =\sum_{i=mid+1}^{r}A[i]((mid-l+1)+(i-mid))^k i=mid+1rA[i](il+1)k=i=mid+1rA[i]((midl+1)+(imid))k

我们设 x = ( m i d − l + 1 ) x=(mid-l+1) x=(midl+1) r s . a n s [ i ] rs.ans[i] rs.ans[i]为区间 [ m i d + 1 , r ] [mid+1,r] [mid+1,r] k = i k=i k=i的答案.

那么原始可以变为:
∑ i = m i d + 1 r A [ i ] ( x + i − m i d ) k = ∑ i = m i d + 1 r ( A [ i ] ∑ j = 0 k C k j x k − j ( i − m i d ) j ) ) \sum_{i=mid+1}^{r}A[i](x+i-mid)^k\\ =\sum_{i=mid+1}^{r}(A[i]\sum_{j=0}^{k}C_{k}^{j}x^{k-j}(i-mid)^j)) i=mid+1rA[i](x+imid)k=i=mid+1r(A[i]j=0kCkjxkj(imid)j))

A [ i ] A[i] A[i]也放进去会得到:
∑ i = m i d + 1 r ∑ j = 0 k A [ i ] C k j x k − j ( i − m i d ) j \sum_{i=mid+1}^{r}\sum_{j=0}^{k}A[i]C_{k}^{j}x^{k-j}(i-mid)^j i=mid+1rj=0kA[i]Ckjxkj(imid)j

我们发现可以用 r s . a n s rs.ans rs.ans代入得到:
∑ j = 0 k C k j x k − j r s . a n s [ j ] \sum_{j=0}^{k}C_{k}^{j}x^{k-j}rs.ans[j] j=0kCkjxkjrs.ans[j]

我们发现这个算式是可以用 O ( k ) O(k) O(k)的时间直接算的,而 k k k又是一个小于 10 10 10的数,所以这样计算右半边并不慢.

那么我们这样就可以把这道题完美AC了.

整个算法的时间复杂度 O ( n k 2 + m k 2 log ⁡ n ) O(nk^2+mk^2\log n) O(nk2+mk2logn),空间复杂度 O ( k ( n + q ) ) O(k(n+q)) O(k(n+q)).

AC代码如下:

#include<bits/stdc++.h>
  using namespace std;
#define Abigail inline void
typedef unsigned int LL;
const int N=200000;
struct tree{
  LL x,ans[11];
  int fa,s[2],size;
}tr[N+9];
LL C[11][11],a[N+9],po[11];
int n,q,root=1,top;
void start(){
  for (int i=0;i<=10;i++){
    C[i][0]=1;C[i][i]=1;
    for (int j=1;j<i;j++)
      C[i][j]=C[i-1][j-1]+C[i-1][j];
  }
}
void pushup(int k){
  int ls=tr[k].s[0],rs=tr[k].s[1];
  tr[k].size=tr[ls].size+tr[rs].size+1;
  po[0]=1LL;
  for (int i=1;i<=10;i++)
    po[i]=po[i-1]*(tr[ls].size+1);
  for (int i=0;i<=10;i++){
    tr[k].ans[i]=tr[ls].ans[i]+tr[k].x*po[i];
    for (int j=0;j<=i;j++)
      tr[k].ans[i]=tr[k].ans[i]+tr[rs].ans[j]*po[i-j]*C[i][j];
  }
}
void rtt(int x){
  int y=tr[x].fa,z=tr[y].fa,k=tr[y].s[1]==x;
  tr[z].s[tr[z].s[1]==y]=x;tr[x].fa=z;
  tr[y].s[k]=tr[x].s[k^1];tr[tr[x].s[k^1]].fa=y;
  tr[x].s[k^1]=y;tr[y].fa=x;
  pushup(y);pushup(x);
}
void splay(int x,int go){
  while (tr[x].fa^go){
    int y=tr[x].fa,z=tr[y].fa;
    if (z^go)
      (tr[y].s[0]==x)^(tr[z].s[0]==y)?rtt(x):rtt(y);
    rtt(x);
  }
  if (!go) root=x;
}
void build(int L,int R){
  int k=++top,mid=L+R>>1;
  tr[k].x=a[mid];
  if (L<=mid-1) tr[k].s[0]=top+1,tr[top+1].fa=k,build(L,mid-1);
  if (R>=mid+1) tr[k].s[1]=top+1,tr[top+1].fa=k,build(mid+1,R);
  pushup(k);
}
int find(int x){
  int u=root;
  while (1){
    if (x<=tr[tr[u].s[0]].size) u=tr[u].s[0];
    else if (x>tr[tr[u].s[0]].size+1) x-=tr[tr[u].s[0]].size+1,u=tr[u].s[1];
      else break;
  }
  splay(u,0);
  return u;
}
void insert(int x,LL v){
  int l=find(x-1),r=find(x);
  splay(l,0);splay(r,l);
  tr[r].s[0]=++top;tr[top].fa=r;
  tr[top].x=v;
  pushup(top);pushup(r);pushup(l);
}
void erase(int x){
  int l=find(x-1),r=find(x+1);
  splay(l,0);splay(r,l);
  tr[r].s[0]=0;
  pushup(r);pushup(l);
}
void change(int x,LL v){
  int l=find(x);
  splay(l,0);
  tr[l].x=v;
  pushup(l);
}
LL query(int l,int r,int k){
  int L=find(l-1),R=find(r+1);
  splay(L,0);splay(R,L);
  return tr[tr[R].s[0]].ans[k];
}
char rc(){
  char c=getchar();
  for (;c<'A'||c>'Z';c=getchar());
  return c;
}
Abigail into(){
  scanf("%d",&n);
  for (int i=1;i<=n;i++)
    scanf("%u",&a[i]);
  scanf("%d",&q);
}
Abigail work(){
  start();      //调了两天突然发现start没有调用... 
  build(0,n+1);
  int l,r,k;
  LL v;
  char opt;
  for (int i=1;i<=q;i++){
    opt=rc();
    switch (opt){
      case 'I':scanf("%d%u",&l,&v);
               insert(l+2,v);
               break;
      case 'D':scanf("%d",&l);
               erase(l+2);
               break;
      case 'R':scanf("%d%u",&l,&v);
               change(l+2,v);
               break;
      case 'Q':scanf("%d%d%d",&l,&r,&k);
               printf("%u\n",query(l+2,r+2,k));
               break;
    }
  }
}
Abigail outo(){
}
int main(){
  into();
  work();
  outo();
  return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值