蓝书(算法竞赛进阶指南)刷题记录——POJ3580 SuperMemo (Splay)

题目:POJ3580.
题目大意:给定一个长度为 n n n的序列,现在有 m m m次操作支持:
1.格式 A D D   x   y   v ADD\,x\,y\, v ADDxyv,表示给区间 [ x , y ] [x,y] [x,y]加上 v v v.
2.格式 R E V E R S E   x   y REVERSE\,x\,y REVERSExy,表示翻转区间 [ x , y ] [x,y] [x,y].
3.格式 R E V O L V E   x   y   k REVOLVE\,x\,y\,k REVOLVExyk,表示把区间 [ x , y ] [x,y] [x,y]旋转 k k k次.
4.格式 I N S E R T   x   y INSERT\,x\,y INSERTxy,表示在第 x x x个位置后面加上一个数字 y y y.
5.格式 D E L E T E   x DELETE\,x DELETEx,表示删除第 x x x个数字.
6.格式 M I N   x   y MIN\,x\,y MINxy,表示求区间 [ x , y ] [x,y] [x,y]的最小值.
1 ≤ n , m ≤ 1 0 5 1\leq n,m\leq 10^5 1n,m105.

一道数据结构裸题,很明显用平衡树来维护.

考虑将位置作为平衡树维护的键值,但是这个键值在插入删除时很难维护所以不存,每次查找一个位置通过平衡树上二分即可.

由于区间操作众多,所以Splay无疑是处理这道题最好的平衡树.

对于区间加,我们可以直接找到位置 x − 1 x-1 x1 y + 1 y+1 y+1所在的节点并把 x − 1 x-1 x1 Splay到根 y + 1 y+1 y+1 Splay成 x − 1 x-1 x1的儿子,那么 y + 1 y+1 y+1的右儿子就是整个区间,我们称之为提取区间,打上懒标记即可.

对于区间翻转,我们先提取出区间,很明显把提取出的区间中所有节点的左右儿子交换就可以做到翻转,维护懒标记即可.

对于区间旋转,容易发现它就是把区间 [ ( y − k + 1 )    m o d    ( y − x + 1 ) , y ] [(y-k+1)\,\,mod\,\,(y-x+1),y] [(yk+1)mod(yx+1),y]放到 x x x的前面,所以大力提取出区间并把表示区间的子树的根的父亲先设为空.之后大力提取区间 [ y , y − 1 ] [y,y-1] [y,y1],把子树根的父亲设为 y y y即可.

对于插入,提取区间 [ x , x − 1 ] [x,x-1] [x,x1]并在 x x x下面新建节点即可.

对于删除,提取区间 [ x , x ] [x,x] [x,x],把 x + 1 x+1 x+1的右儿子设为空即可.

对于区间最小值,直接提取区间输出最小即可.

时间复杂度 O ( n + m log ⁡ n ) O(n+m\log n) O(n+mlogn).

代码如下:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
  using namespace std;
 
#define Abigail inline void
typedef long long LL;
 
const int N=100000,INF=(1<<31)-1;
//INF一定要开到2^31-1,不然会被卡
 
int n,a[N+9];
struct tree{
  int x,fa,s[2],siz,min,add,rev;
  tree(){x=fa=s[0]=s[1]=siz=add=rev=0;min=INF;}
  //构造函数,初始min=INF,为了避免Pushup时要判断儿子是否为空的情况
}tr[N*2+9];
int cn,rot,tmp[N*2+9],tt;
 
void Pushup(int k){
  int ls=tr[k].s[0],rs=tr[k].s[1];
  tr[k].siz=tr[ls].siz+tr[rs].siz+1;
  tr[k].min=min(tr[k].x,min(tr[ls].min,tr[rs].min));
}
 
void Update_rev(int k){if (!k) return;swap(tr[k].s[0],tr[k].s[1]);tr[k].rev^=1;}
void Update_add(int k,int v){if (!k) return;tr[k].add+=v;tr[k].x+=v;tr[k].min+=v;}
//建议要判掉k=0的情况不然可能会溢出

void Pushdown_rev(int k){
  if (!tr[k].rev) return;
  Update_rev(tr[k].s[0]);Update_rev(tr[k].s[1]);
  tr[k].rev=0;
}
 
void Pushdown_add(int k){
  if (!tr[k].add) return;
  Update_add(tr[k].s[0],tr[k].add);Update_add(tr[k].s[1],tr[k].add);
  tr[k].add=0;
}
 
void Pushdown(int k){Pushdown_rev(k);Pushdown_add(k);}
 
void Rotate(int x){
  int y=tr[x].fa,z=tr[y].fa,k=tr[y].s[1]==x;
  if (z) tr[z].s[tr[z].s[1]==y]=x;tr[x].fa=z;
  tr[y].s[k]=tr[x].s[k^1];if (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){
  tmp[tt=1]=x;
  for (int i=x;tr[i].fa;i=tr[i].fa) tmp[++tt]=tr[i].fa;
  for (;tt;--tt) Pushdown(tmp[tt]);
  for (int y,z;tr[x].fa^go;Rotate(x)){
    y=tr[x].fa;z=tr[y].fa;
    if (z^go) tr[z].s[0]==y^tr[y].s[0]==x?Rotate(x):Rotate(y);
    //一定要在这里写z^go而不是直接写z
  }
  if (!go) rot=x;
  Pushup(x);      //这道题虽然没卡,但这个Pushup有时候不写会出事
}
 
int New_node(int x,int fa){
  tr[++cn]=tree();
  tr[cn].x=tr[cn].min=x;tr[cn].siz=1;
  tr[cn].fa=fa;
  return cn;
}
 
void Build(int l,int r,int fa=0,int &k=rot){
  int mid=l+r>>1;
  k=New_node(a[mid],fa);
  if (l<mid) Build(l,mid-1,k,tr[k].s[0]);
  if (r>mid) Build(mid+1,r,k,tr[k].s[1]);
  Pushup(k);
}
 
int Find_rank(int p,int rot=rot){
  ++p;
  int k=rot;
  while (2333){
    Pushdown_rev(k);
    //翻转标记会改变树的形态,在查询第k个位置时一定要下传翻转标记
    if (p>tr[tr[k].s[0]].siz+1) p-=tr[tr[k].s[0]].siz+1,k=tr[k].s[1];
    else if (p<=tr[tr[k].s[0]].siz) k=tr[k].s[0];
      else break;
  }
  Splay(k,0);
  return k;
}
 
int Insert(int p,int x){
  int l=Find_rank(p),r=Find_rank(p+1);
  Splay(l,0);Splay(r,l);
  tr[r].s[0]=New_node(x,r);
  Splay(tr[r].s[0],0);
  return 0;
}
 
int Extract(int L,int R){
  int l=Find_rank(L-1),r=Find_rank(R+1);
  Splay(l,0);Splay(r,l);
  return tr[r].s[0];
}
 
void Erase(int p){int k=tr[Extract(p,p)].fa;tr[k].s[0]=0;Splay(k,0);}
void Change_add(int L,int R,int v){int k=Extract(L,R);Update_add(k,v);Splay(k,0);}
void Change_rev(int L,int R){int k=Extract(L,R);Update_rev(k);Splay(k,0);}
int Query_min(int L,int R){int k=Extract(L,R),res=tr[k].min;Splay(k,0);return res;}
 
void Change_spin(int L,int R,int x){
  x%=(R-L+1);
  if (!x) return;
  int l=Find_rank(L-1),r=Find_rank(L),t=Extract(R-x+1,R);
  tr[tr[t].fa].s[0]=0;Splay(tr[t].fa,0);
  Splay(l,0);Splay(r,l);
  tr[r].s[0]=t;tr[t].fa=r;
  Splay(t,0);
}
 
Abigail into(){
  scanf("%d",&n);
  for (int i=1;i<=n;++i)
    scanf("%d",&a[i]);
}
 
Abigail work(){
  Build(0,n+1);
}
 
Abigail getans(){
  char c[10];
  int q,x,y,z;
  scanf("%d",&q);
  while (q--){
    scanf("%s",c);
    switch (c[0]){
      case 'A':
        scanf("%d%d%d",&x,&y,&z);
        Change_add(x,y,z);
        break;
      case 'R':
        if (c[3]=='E'){
          scanf("%d%d",&x,&y);
          Change_rev(x,y);
        }else{
          scanf("%d%d%d",&x,&y,&z);
          Change_spin(x,y,z);
        }
        break;
      case 'I':
        scanf("%d%d",&x,&y);
        Insert(x,y);
        break;
      case 'D':
        scanf("%d",&x);
        Erase(x);
        break;
      case 'M':
        scanf("%d%d",&x,&y);
        printf("%d\n",Query_min(x,y));
        break;
    }
  }
}
 
int main(){
  into();
  work();
  getans();
  return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值