题目描述:
给你n个由1到n 数列,让你进行m次操作。每次操作给你一个区间[l,r],让你将区间[l,l+r-1]的数移动到开头。问你m次操作后的数列的顺序。
题目分析:
这个题目中对数列的操作很明显是让我们通过平衡树的操作,让我们将[0,L]的数删除,并将这一段的数再拼接到[L,R]后面。直接码一颗平衡树即可通过。
但是!!在C++库中,其实是封装了一个可持久性平衡树。在这个题上我们可以直接套用库内封装的可持久性平衡树进行调用即可。(但是,库内的平衡树常数相对来说比较大,这次出题人貌似没有特意取卡,so,以后能够自己手写还是手写的好 滑稽脸.jpg)
代码:
#include<bits/stdc++.h>
#include<ext/rope>
using namespace std;
using namespace __gnu_cxx;
rope<int>s,tmp;
int n,m;
int main(){
scanf("%d %d",&n,&m);
for(int i=0;i<n;i++) s.insert(i,i+1);
int l,r;
while(m--){
scanf("%d%d",&l,&r);
tmp=s.substr(l-1,r);
s.erase(l-1,r);
s.insert(0,tmp);
}
for(int i=0;i<n-1;i++) printf("%d ",s[i]);
printf("%d
",s[n-1]);
return 0;
}
顺便还是附上平衡树的板子吧:
#include <bits/stdc++.h>
using namespace std;
const int T=1e6+6; // 节点数量, 同时也是所维护的序列的长度
int lch[T], rch[T], siz[T], fix[T], n; int key[T];
// lch[t]: left child, t 的左孩子
// rch[t]: right child, t 的右孩子
// siz: t 子树的规模
// fix: t 节点的随机权值,越大的位置越高
// key: t 节点的关键字值
int tot, root;
// tot: 已使用节点数量,包括空节点 (0 号元素) 在内
// root: 如等于 0,表示空树,否则表示一棵 treap 的根节点
inline void reset() {root=0; tot=1;}
// 新建一个节点,生成随机权值,保证高度为期望 O(log T), 并令它的关键字为 x, 返回节点的编号
inline int newnode(int x){
int t=tot; ++tot; /* assert(tot<=T); */
lch[t]=rch[t]=0, siz[t]=1; // siz 域是要把自己也算进去的
fix[t]=rand()<<15|rand(); // 普通的 rand() 只能产生 (1<<15) 以内的整数,需要做两次之后拼接起来
key[t]=x;
return t;
}
// 如果你需要懒标记,可以把维护过程补在这里,比如说区间翻转,区间求最值等操作(本程序没有体现,故为空白,连形参都省略)
inline void pushdown(int){}
// 如无上述特殊询问,update 负责维护 siz 域
inline void update(int t){
siz[t]=siz[lch[t]]+1+siz[rch[t]];
}
// 把 t 以第 k 名为界分割成 l 和 r 两半,k 按人类的思维从 1 开始编号,其中第 k 名将落入 r 中。
void split(int t, int k, int &l, int &r){
if(!t){l=r=0; /*assert(k==1);*/ return;}
pushdown(t);
int tk=siz[lch[t]]+1;
if(k<=tk){
split(lch[t], k, l,lch[t]); r=t;
}else{
split(rch[t], k-tk, rch[t],r); l=t;
}
update(t);
}
// 将 l 和 r 合并成 t,如合并后以中序遍历查看 t 的内容,则有 l 的内容排在 r 的前面。
void join(int &t, int l, int r){
if(!l){t=r; return;}
if(!r){t=l; return;}
if(fix[l]>fix[r]){
pushdown(t=l); join(rch[l],rch[l],r);
}else{
pushdown(t=r); join(lch[r],l,lch[r]);
}
update(t);
}
// 在 t 中寻找第 k 名元素,并返回其编号(注意是编号,不是其关键字)(这在设计上和 STL set 的 find 函数如出一辙)
int find(int t, int k){
pushdown(t);
int tk=siz[lch[t]]+1;
if(k==tk)return t;
if(k<tk) return find(lch[t],k);
return find(rch[t],k-tk);
}
// 查询 t 中第一个大于等于 x 的关键字(这在设计上和 STL set 的 lower_bound 函数也是如出一辙)
int order(int t, int x){
if(!t)return 1;
pushdown(t);
if(x<=key[t])return order(lch[t],x);
return siz[lch[t]]+1+order(rch[t],x);
}
inline void insert(int x){
int l, r, k=order(root,x), t=newnode(x);
split(root, k, l, r);
join(r, t, r);
join(root, l, r);
}
void work(int p, int len){
int l, r, m;
split(root, p, l, r);
split(r, len+1, m, r);
join(root, l, r);
join(root, m, root);
}
vector<int> ans;
void display(int u){
if(u==0)return;
display(lch[u]);
ans.push_back(u);
display(rch[u]);
}
int main(){
#ifdef LOCAL
freopen("in.txt","r",stdin);
#endif // LOCAL
srand(19970518u); // 有些比赛或 OJ 不能用 time(), 这个时候你可以把你的生日号填进去,数字后面的 u 表示这是无符号数,你也可以省略不写。
int zo;
while(~scanf("%d%d",&n,&zo)){
reset();
for(int i=1; i<=n; ++i){
insert(i);
}
for(int i=0; i<zo; ++i){
int l, len; scanf("%d%d",&l,&len);
work(l,len);
}
ans.clear();
display(root);
int zans=(int)ans.size();
for(int i=0; i<zans; ++i){
printf("%d%c",ans[i],"
"[i==zans-1]);
}
}
return 0;
}