splay的区间平移与翻转操作
Hdu 3487 play the chain
瑶瑶很喜欢玩项链,她有一根项链上面有很多宝石,宝石从1到n编号。
首先,项链上的宝石的编号组成一个序列:1,2,3,...,n。
她喜欢两种操作:
1.CUT a b c:他会先将a至b号宝石切下来,然后接到c号宝石后面,组成一个新的项链。
举个例子,如果n=8,那么这个项链上的宝石编号依次为:1 2 3 4 5 6 7 8;'CUT 3 5 4',首先我们把3到5号宝石切下
项链变成了:1 2 6 7 8;然后接到4号宝石后面,此时的4号宝石为7,所以此时的项链变成了:1 2 6 7 3 4 5 8.
2.FLIP a b:像第一个操作一样我们先将a至b号宝石切下来,然后将其旋转180°,变成与原来相反的链,在插入到项链的相 同位置中。
举个例子,取操作1中的链:1 2 3 4 5 6 7 8,执行FLIP 2 6操作,则项链将变成:1 6 5 4 3 2 7 8.
他想知道经过m个操作之后项链会变成怎样。
输入
对于每一个数据,第一行会有两个整数:n m(1<=n,m<=300000) n代表宝石的个数,m代表操作的个数。
接下来有M行 有两个操作:
CUT A B C //代表CUT操作,1<=A<=B<=N, 0<=C<=N-(B-A+1).
FLIP A B //代表FLIP操作,1<=A<=B<=N.
输出的结尾将会有两个负数,他们不能当做操作.
输出
对于每一个数据,你需要输出N个整数,任两个数字间用一个空格分开,代表最终得到的项链的从1到N的宝石的序列号。
样例输入
8 2
CUT 3 5 4
FLIP 2 6
-1 -1
样例输出
1 4 3 7 6 2 5 8
将1,2,3,4,5,6,7,8中的3,4移到6的后面,7的前面
设a=3,b=5,c=7.
原图为
先将a=3旋转为根,此为定好开始点,得到
再将b=5旋转为根,此为定好结束点,目标区域为[a,b)这一段,得到
发现3现在的父亲不是5,于是将3旋转一次,得到
此时我们的目标区域已出来,在以3为根的树及其右子树,于是将其拿出来。将3的左子树直接连成5的左子树(因为3的左子树在今后的中序遍历中,要先于5访问),然后找到c=7这个点,变成7的左子树。如果此时7的左子树不为空,则将其变为3的左子树(理由如下,目标区域要先于7访问)
如果是将1 2 5 6 3 4 7 8中的5移到2的前面,变成1 5 2 6 3 4 7 8,则进行图如下
原图为:
先变成:
拿出以5为根及其右子树(此时为空)
将其做为2的左子树,2从前的左子树变成5的左子树,得到
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define inf 0x7f7f7f7f
using namespace std;
typedef long long ll;
typedef unsigned int ui;
typedef unsigned long long ull;
inline int read()
{
int x=0,f=1;char ch=getchar();
for (;ch<'0'||ch>'9';ch=getchar()) if (ch=='-') f=-1;
for (;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+ch-'0';
return x*f;
}
inline void print(int x)
{
if (x>=10) print(x/10);
putchar(x%10+'0');
}
const int N=3e5;
int n,m;
struct Splay
{
#define T(x) (tree[f[x]][1]==x)
int tree[N+10][2],f[N+10],size[N+10];
bool flag[N+10];
int root,cnt;
void clear()
{
root=cnt=0;
memset(f,0,sizeof(f));
memset(tree,0,sizeof(tree));
memset(size,0,sizeof(size));
memset(flag,0,sizeof(flag));
}
void pushdown(int x)
{
if (!flag[x]) return;
swap(tree[x][0],tree[x][1]);
flag[tree[x][0]]^=1;
flag[tree[x][1]]^=1;
flag[x]=0;
}
void write(int x)
{
if (!x) return;
pushdown(x);
write(tree[x][0]);
if (x<=n) ++cnt!=n?printf("%d ",x):printf("%d\n",x);
write(tree[x][1]);
}
void build()
{
root=n+1;
for (int i=1;i<=n;i++)
f[i]=i+1,size[i]=i+1,tree[i+1][0]=i;
//建立一条链出来,1在最下面,n在最上面
f[n+2]=1,size[n+2]=1,tree[1][0]=n+2;
//1的左子树挂一个节点
size[root]=n+2;
}
void updata(int x)
{
size[x]=size[tree[x][0]]+size[tree[x][1]]+1;
}
void move(int x) //一次旋转操作
{
int fa=f[x],son=tree[x][T(x)^1];
tree[x][T(x)^1]=fa;
tree[fa][T(x)]=son;
if (son)
f[son]=fa;
f[x]=f[fa];
if (f[x])
tree[f[x]][T(fa)]=x;
f[fa]=x;
updata(fa),updata(x);
}
void splay(int x) //将x变成根
{
while (f[x])
{
if (f[f[x]])
T(x)==T(f[x])?move(f[x]):move(x);
move(x);
}
root=x;
}
int find(int x,int i)
{
if (!i) return 0;
pushdown(i);
if (size[tree[i][0]]+1==x)
return i;
if (x<=size[tree[i][0]])
return find(x,tree[i][0]);
return
find(x-size[tree[i][0]]-1,tree[i][1]);
}
void flip()
{
int x=read(),y=read();
x=find(x,root),splay(x);
//x为我们要翻转区间的前一个数字
//我们要以x为根,将目标区域变成其右子树,
//这个与cut操作稍有不同。因为翻转时涉及到标记下传
//所以应该将所有结点做成一棵整个的树,
//cut的时候是进行移动,移动就需要从一个开始点开始进行
//于是让这个开始点做为根,所以其它结点做为其右子树。
//对于文章中的配图,就是将[4,4]这一段翻转
y=find(y+2,root),splay(y);
if (f[x]!=root)
move(x);
//以上为挖出所要区间来
flag[tree[x][1]]^=1;
}
void consert(int x,int fa)
//如果x是fa的子结点的话,则cut之
//否则就将x变成fa的左子结点
{
if (tree[f[x]][T(x)]==x)
tree[f[x]][T(x)]=0;
f[tree[fa][0]=x]=fa;
}
void up(int x)
{
while (f[x])
updata(x),x=f[x];
}
void cut()
{
//1,2,3,4,5,6,7,8
//把[3,4]放到6后面
int x=read(),y=read(),z=read();
//x=3,y=4,z=6
//注意前面开了一个空结点
x=find(x+1,root),splay(x);//x=3
y=find(y+2,root),splay(y);//y=5,也就是找到[3,4]后面那个点
if (f[x]!=root)
//如果3的父亲不是根,就要旋转一次
move(x);
consert(tree[x][0],f[x]);
//将3的左子结点变为5的左子结点
//以3为根的(含右子树)拿出来
z=find(z+2,root);// 找到6后面那个点7
consert(tree[z][0],x);//将7的左子结点变为3的左子结点
consert(x,z);//将3变为7的左子结点
up(x);//更新节点信息
}
}T;
char s[10];
int main(){
while (1){
T.clear();
n=read(),m=read();
if (n==-1&&m==-1) break;
T.build();
for (int i=1;i<=m;i++)
{
scanf("%s",s);
if (s[0]=='F') T.flip();
if (s[0]=='C') T.cut();
}
T.write(T.root);
}
return 0;
}
序列终结者
给定一个长度为N的序列,每个序列的元素是一个整数(废话)。
要支持以下三种操作:
将[L,R]这个区间内的所有数加上V。
将[L,R]这个区间翻转,比如1 2 3 4变成4 3 2 1。
求[L,R]这个区间中的最大值。 最开始所有元素都是0。
输入
第一行两个整数N,M。M为操作个数。
以下M行,每行最多四个整数,依次为K,L,R,V。K表示是第几种操作,如果不是第1种操作则K后面只有两个数。
输出
对于每个第3种操作,给出正确的回答。
样例
输入
4 4
1 1 3 2
1 2 4 -1
2 1 3
3 2 4
输出
2
#include<cstdio> #include<algorithm> #include<cstring> #define p(x) (s[fa[x]][1]==x) using namespace std; const int maxn=50010; int n,m,root,summin,summax; int fa[maxn],s[maxn][2],v[maxn],siz[maxn],v_max[maxn],lazy_flip[maxn],lazy_sum[maxn]; int read() { int x=0,f=1;char ch=getchar(); for (;ch<'0'||ch>'9';ch=getchar()) if(ch=='-') f=-1; for (;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; return x*f; } void pushdown(int x) { if (lazy_sum[x]) { if (s[x][0]) v[s[x][0]]+=lazy_sum[x], v_max[s[x][0]]+=lazy_sum[x], lazy_sum[s[x][0]]+=lazy_sum[x]; if (s[x][1]) v[s[x][1]]+=lazy_sum[x], v_max[s[x][1]]+=lazy_sum[x], lazy_sum[s[x][1]]+=lazy_sum[x]; lazy_sum[x]=0; } if (lazy_flip[x]) { swap(s[x][0],s[x][1]); if (s[x][0]) lazy_flip[s[x][0]]^=1; if (s[x][1]) lazy_flip[s[x][1]]^=1; lazy_flip[x]=0; } } void updata(int x) { siz[x]=siz[s[x][0]]+siz[s[x][1]]+1; v_max[x]=v[x]; if (s[x][0]) v_max[x]=max(v_max[x],v_max[s[x][0]]); if (s[x][1]) v_max[x]=max(v_max[x],v_max[s[x][1]]); } void move(int x) { int f=fa[x],t=p(x),son=s[x][1-t]; s[f][t]=son; if (son) fa[son]=f; fa[x]=fa[f]; if (fa[f]) s[fa[f]][p(f)]=x; fa[f]=x; s[x][1-t]=f; updata(f),updata(x); } void splay(int x,int f) //将x旋转成为f的子结点 { while(fa[x]!=f) { if (fa[fa[x]]!=f) { if (p(x)==p(fa[x])) move(fa[x]); else move(x); } move(x); } if (!f) root=x; return; } int find(int x,int k) { pushdown(x); if (siz[s[x][0]]+1==k) return x; if (siz[s[x][0]]>=k) return find(s[x][0],k); return find(s[x][1],k-siz[s[x][0]]-1); } void build(int l,int r,int f,int t) //对于区间[l,r]其父亲点是f,t代表是上一个区间的左边还是右边 { if (l>r) return; int mid=(l+r)>>1; siz[mid]=1; fa[mid]=f; if (f) //f不为0时,f结点的t儿子为mid点 s[f][t]=mid; else //否则设mid为root点,mid的父亲点为0号点 root=mid; build(l,mid-1,mid,0),build(mid+1,r,mid,1); siz[mid]+=siz[s[mid][0]]+siz[s[mid][1]]; return; } int main() { n=read(),m=read(); build(1,n+2,0,0); //开始时数列为空!!!,最前面及最后面加一个空结点 for (int i=1;i<=m;i++) { int k=read(),l=read(),r=read(),sl=find(root,l),sr=find(root,r+2); // 原数列 1, 2, 3, 4, 5, 6, 7, 8 // | | | | | | | | //新数列 2, 3, 4, 5, 6, 7, 8, 9 splay(sl,0); //因为前面有个空结点,所以sl其实为l左边那个点,将sl做为根结点 splay(sr,sl); //sr做为sl的右子结点 //sr为从前r右边的那个点 if (k==1) { int sum=read(); v_max[s[sr][0]]+=sum; //目标区域为sr的左子树 v[s[sr][0]]+=sum; lazy_sum[s[sr][0]]+=sum; } if (k==2) lazy_flip[s[sr][0]]^=1; if (k==3) printf("%d\n",v_max[s[sr][0]]); } return 0; }
/* 目标区间为[x,y],将x前面一个位置a先旋成根 再将y后面那个位置b旋转成根 再让a做为b的左子点 于是目标区域就是x的右子树了 当然也可以让a做root b做为a的右子结点 目标区域为b的左子结点 */ #include<cmath> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define inf 0x7f7f7f7f using namespace std; typedef long long ll; typedef unsigned int ui; typedef unsigned long long ull; inline int read(){ int x=0,f=1;char ch=getchar(); for (;ch<'0'||ch>'9';ch=getchar()) if (ch=='-') f=-1; for (;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+ch-'0'; return x*f; } inline void print(int x){ if (x>=10) print(x/10); putchar(x%10+'0'); } const int N=5e4; struct Splay { #define T(x) (tree[f[x]][1]==x) #define ls(x) tree[x][0] #define rs(x) tree[x][1] int tree[N+10][2],size[N+10],f[N+10],val[N+10],cnt[N+10],Max[N+10]; bool rev[N+10]; int root,len; void build(int n) { root=n+1; for (int i=1;i<=n;i++) f[i]=size[i]=i+1,ls(i+1)=i; f[ls(1)=n+2]=1,Max[n+2]=val[n+2]=-inf,size[n+2]=1; size[root]=n+2,Max[root]=val[root]=inf; } void Rev(int x) { swap(ls(x),rs(x)); rev[ls(x)]^=1,rev[rs(x)]^=1; rev[x]=0; } void Inc(int x) { cnt[ls(x)]+=cnt[x],Max[ls(x)]+=cnt[x],val[ls(x)]+=cnt[x]; cnt[rs(x)]+=cnt[x],Max[rs(x)]+=cnt[x],val[rs(x)]+=cnt[x]; cnt[x]=0; } void pushdown(int x) { if (rev[x]) Rev(x); if (cnt[x]) Inc(x); } void updata(int x) { size[x]=size[ls(x)]+size[rs(x)]+1; Max[x]=val[x]; if (ls(x)) Max[x]=max(Max[x],Max[ls(x)]); if (rs(x)) Max[x]=max(Max[x],Max[rs(x)]); } void move(int x) { int fa=f[x],son=tree[x][T(x)^1]; tree[x][T(x)^1]=fa; tree[fa][T(x)]=son; if (son) f[son]=fa; f[x]=f[fa]; if (f[x]) tree[f[x]][T(fa)]=x; f[fa]=x; updata(fa),updata(x); } void up(int x) { if (!x) return; up(f[x]); pushdown(x); } void splay(int x) { // up(x); while (f[x]) { if (f[f[x]]) T(x)==T(f[x])?move(f[x]):move(x); move(x); } root=x; } int find(int x,int i) { pushdown(i); if (size[ls(i)]+1==x) return i; if (x<=size[ls(i)]) return find(x,ls(i)); return find(x-size[ls(i)]-1,rs(i)); } void ADD(int x,int y,int v) //目标区间为[x,y],将x前面一个位置先旋成根 //再将y后面那个位置旋转成根 //再让x做为y的左子点 //于是目标区域就是x的右子树了 { x=find(x,root),splay(x); y=find(y+2,root),splay(y); if (f[x]!=root) move(x); cnt[rs(x)]+=v; Max[rs(x)]+=v; val[rs(x)]+=v; } void filp(int x,int y) { x=find(x,root),splay(x); y=find(y+2,root),splay(y); if (f[x]!=root) move(x); rev[rs(x)]^=1; } void query(int x,int y){ x=find(x,root),splay(x); y=find(y+2,root),splay(y); if (f[x]!=root) move(x); printf("%d\n",Max[rs(x)]); } }T; int main(){ int n=read(),m=read(); T.build(n); for (int i=1;i<=m;i++){ int k=read(),x=read(),y=read(); if (k==1) T.ADD(x,y,read()); if (k==2) T.filp(x,y); if (k==3) T.query(x,y); } return 0; }
在建立splay树时,为了更均衡点,可以采用类似线段树的方式,例如有10个结点时:
#include<bits/stdc++.h>
using namespace std;
#define maxn 100050
int n,ch[maxn][2],f[maxn],size[maxn],num[maxn];
int root,sz,rev[maxn],add[maxn],mx[maxn],m;
struct node{
int s,id;
}a[maxn];
int p[maxn];
void read(int &x) {
char ch; bool ok;
for(ok=0,ch=getchar(); !isdigit(ch); ch=getchar()) if(ch=='-') ok=1;
for(x=0; isdigit(ch); x=x*10+ch-'0',ch=getchar()); if(ok) x=-x;
}
void pushdown(int x)
{
int l=ch[x][0],r=ch[x][1];
if(rev[x])
{
rev[l]^=1;rev[r]^=1;
swap(ch[x][0],ch[x][1]);
rev[x]=0;
}
if(add[x])
{
int ad=add[x];
if(l) num[l]+=ad,mx[l]+=ad,add[l]+=ad;
if(r) num[r]+=ad,mx[r]+=ad,add[r]+=ad;
add[x]=0;
}
}
void update(int x)
{
size[x]=1+size[ch[x][0]]+size[ch[x][1]];
mx[x]=max(max(mx[ch[x][1]],mx[ch[x][0]]),num[x]);
}
inline void rotate(int &k,int x)
{
int y=f[x],z=f[y],l,r;
if(ch[y][0]==x) l=0;
else l=1;
r=l^1;
if(k==y) k=x;
else
{
if(ch[z][0]==y) ch[z][0]=x;
else ch[z][1]=x;
}
f[x]=z;f[y]=x;f[ch[x][r]]=y;
ch[y][l]=ch[x][r];ch[x][r]=y;
update(y);update(x);
}
inline void splay(int &k,int x)
{
while(x!=k)
{
int y=f[x],z=f[y];
if(y!=k)
{
if((ch[y][0]==x)^(ch[z][0]==y))
rotate(k,x);
else
rotate(k,y);
}
rotate(k,x);
}
}
inline void build(int l,int r,int last)
{
if (l>r) return;
if (l==r)
{
f[l]=last;
size[l]=1;
if(l<last)
ch[last][0]=l;
else
ch[last][1]=l;
return;
}
int mid=(l+r)>>1;
build(l,mid-1,mid);
build(mid+1,r,mid);
f[mid]=last;
update(mid);
if (mid<last)
ch[last][0]=mid;
else
ch[last][1]=mid;
}
int find(int k,int rank)
{
pushdown(k);
int l=ch[k][0],r=ch[k][1];
if(size[l]+1==rank) return k;
else if(size[l]>=rank) return find(l,rank);
else return find(r,rank-size[l]-1);
}
void Add(int l,int r,int v)
{
int x=find(root,l),y=find(root,r+2);
splay(root,x);
splay(ch[x][1],y);
int z=ch[y][0];
add[z]+=v,mx[z]+=v,num[z]+=v;
}
void reverse(int l,int r)
{
int x=find(root,l),y=find(root,r+2);
splay(root,x);
splay(ch[x][1],y);
rev[ch[y][0]]^=1;
}
void query(int l,int r)
{
int x=find(root,l),y=find(root,r+2);
splay(root,x),splay(ch[x][1],y);
printf("%d\n",mx[ch[y][0]]);
}
int main()
{
mx[0]=-0x7ffffff;
read(m),read(n);
build(1,m+2,0);
root=(m+3)>>1;
for(int i=1;i<=n;i++)
{
int flag,l,r,val;
read(flag),read(l),read(r);
if(flag==1) read(val),Add(l,r,val);
else if(flag==2) reverse(l,r);
else query(l,r);
}
}