整理的算法模板合集: ACM模板
c++自带的可持久化平衡树?rope大法好!
C++11及以后开始支持
可以当作可持久化平衡树使用,内部构造是一个块状链表
1. 声明
1)头文件
#include<ext/rope>
2)调用命名空间
using namespace __gnu_cxx;
3)声明使用
rope<int>Irp;
rope<long long>Lrp;
crope crp;//相当于定义成rope<char>,即定义为string类型
rope<char>rop;
rope<double>Drp;
//rope<PII>Prp;不合法
2. 支持操作
push_back(x); 在末尾添加x
insert(pos,x); 在pos插入x
erase(pos,x); 从pos开始删除x个
replace(pos,x); 从pos开始换成x
substr(pos,x); 提取pos开始x个
at(x)/[x]; 访问第x个元素
test.clear(); 清空元素
char类型的rope
insert(int pos, string &s, int n);
将字符串s的前n位插入rope的下标pos处
append(string &s,int pos,int n);
把字符串s中从下标pos开始的n个字符连接到rope的结尾,如没有参数n则把字符串s中下标pos后的所有字符连接到rope的结尾,如没有参数pos则把整个字符串s连接到rope的结尾
substr(int pos, int len);
提取rope的从下标pos开始的len个字符
at(int x);
访问rope的下标为x的元素
erase(int pos, int num);
从rope的下标pos开始删除num个字符
copy(int pos, int len, string &s);
从rope的下标pos开始的len个字符用字符串s代替,如果pos后的位数不够就补足
replace(int pos, string &x);
从rope的下标pos开始替换成字符串x,x的长度为从pos开始替换的位数,如果pos后的位数不够就补足
int n, m;
char a[N];
rope<char>r;
int main()
{
for(int i = 0; i <= 10; ++ i)
a[i] += i +'0';
r.insert(0, a + 2, 8);
for(int i = 0; i <= 10; ++ i)
cout << r.at(i) << " ";
puts("");
for(int i = 0; i <= 10; ++ i)
cout << r[i] << " ";
puts("");
return 0;
}
2 3 4 5 6 7 8 9
2 3 4 5 6 7 8 9
int类型的rope
insert(int pos, int *s, int n);
将int数组(以下的s都是int数组)s的前n位插入rope的下标pos处,如没有参数n则将数组s的所有位都插入rope的下标pos处
append(int *s,int pos,int n);
把数组s中从下标pos开始的n个数连接到rope的结尾,如没有参数n则把数组s中下标pos后的所有数连接到rope的结尾,如没有参数pos则把整个数组s连接到rope的结尾
substr(int pos, int len);
提取rope的从下标pos开始的len个数
at(int x);
访问rope的下标为x的元素
erase(int pos, int num);
从rope的下标pos开始删除num个数
copy(int pos, int len, int *s);
从rope的下标pos开始的len个数用数组s代替,如果pos后的位数不够就补足
replace(int pos, int *x);
从rope的下标pos开始替换成数组x,x的长度为从pos开始替换的位数,如果pos后的位数不够就补足
rope<int>rp;
int main()
{
rp.append(3);
rp.append(1);
rp.append(2);
rp.append(1);
rp = rp.substr(1, 3);//从1开始截3个数字,注意rope是从0开始的,所有的容器都是从0开始的
for(int i = 0; i < rp.size(); ++ i)
cout << rp[i] << " ";
puts("");
return 0;
}
输出:
1 2 1
3. 具体的细节
时间复杂度: O ( n n ) O(n\sqrt{n}) O(nn)(因为是用块状链表实现的)
空间复杂度: O ( n ) O(\sqrt{n}) O(n)
4. “可持久化”
定义方法
rope<char> *now[p];
如何申请更新一个新的可持久化版本:
now[0]=new rope<char>();
如何继承版本
now[cnt]=new rope<char>(*now[cnt-1]);
如何回到查询过去的版本
ans=now[cnt]->at(num);
这样我们就得到了一个可持久化线段树!
5. 例题
1. luogu P3919 【模板】可持久化线段树 1(可持久化数组)【代替可持久化线段树】
数据太大了…
1
e
6
1e6
1e6…
由于本题的时空限制比较严格,所以只能拿94分,最后一个点MLE…一般的题目不会卡的这么严。
这道题因为有M个版本,也就是说空间复杂度为 O ( n m ) O(n\sqrt{m}) O(nm) = 1 e 9 = 1e9 =1e9,也就是最大开了 1 e 9 1e9 1e9个 i n t int int,一个 i n t int int占4个字节,也就是B,1KB=1024B,1MB=1024KB,1Gb=1024MB,妈呀,我算了一下,我这个程序在最后一个点的时候用了大概3814MB,三个G的内存hhh…
//#pragma GCC optimize("Ofast")
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<ext/rope>
#define debug(x) cout << x << "ok" << endl
typedef long long ll;
#define file freopen("1.in", "r", stdin);freopen("1.out", "w", stdout);
const int N = 1e6 + 1, M = 1e5 + 7, INF = 0x3f3f3f3f;
using namespace std;
using namespace __gnu_cxx;
//可持久化平衡树
typedef pair<int, int> PII;
int n, m;
int a[N];
rope<int> *S[N];
ll read()
{
ll x = 0, f = 1;char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-')f = -1;ch = getchar();}
while(ch >= '0' && ch <= '9'){x = x * 10 + ch - '0';ch = getchar();}
return x * f;
}
int main()
{
n = read(), m = read();
S[0] = new rope<int>();
S[0]->append(0);
for(int i = 0; i < n; ++ i)
a[i] = read();
S[0]->insert(1, a, n);//从1开始(如果不使用这个方法而是每次输入一个放进去一个有个点会MLE...)
//for(int i = 0; i <= n; ++ i)
// cout << S[0]->at(i) << endl;
for(int i = 1; i <= m; ++ i){
int v, k, a, b;
v = read(), k = read();
S[i] = new rope<int>(*S[v]);
if(k == 1){
a = read(), b = read();
S[i]->replace(a, b);
}
else {
a = read();
printf("%d\n", S[i]->at(a));
}
}
return 0;
}
2. 牛客 Shuffle Cards 【代替Splay】
- 首先分析数据范围。
1 e 5 1e5 1e5,加上是区间移动,一看就是一个数据结构问题,区间问题一般用Splay解决,但是我只会treap…。但是我们有rope这个神器!
- 然后来分析题意。
题目大意:有n个数,m次操作,每次选择从x开始长度为y的数放到数组的最前面。问m次操作后的序列。直接用rope暴力跑啊!
//#pragma GCC optimize("Ofast")
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<bitset>
#include<map>
#include<ext/rope>
#define debug(x) cout << x << "ok" << endl
typedef long long ll;
#define file freopen("1.in", "r", stdin);freopen("1.out", "w", stdout);
const int N = 2e5 + 7, M = 1e5 + 7, INF = 0x3f3f3f3f;
using namespace std;
using namespace __gnu_cxx;
ll read()
{
ll x = 0, f = 1;char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-')f = -1;ch = getchar();}
while(ch >= '0' && ch <= '9'){x = x * 10 + ch - '0';ch = getchar();}
return x * f;
}
int n, m, x, y;
rope<int>r;
int a[N];
int main()
{
n = read(), m = read();
//for(int i = 1; i <= n; ++ i)
// r.push_back(i);
//r.append(0);
for(int i = 1; i <= n; ++ i)
a[i] = i;
r.insert(0, a + 1, n);//这样写会比上面那种每次都放到后面快一点
while(m -- ){
x = read(), y = read();
r = r.substr(x - 1, y) + r.substr(0, x - 1) + r.substr(x + y - 1, n - x - y + 1);
}
for(int i = 0; i < n; ++ i){
printf("%d%s", r[i], i == (n - 1) ? "\n" : " ");
}
return 0;
}
3. luogu P3402 可持久化并查集【代替可持久化并查集】
实际写法和可持久化线段树一样开一堆数组,直接暴力维护
注意:BZOJ 3673: 可持久化并查集可以AC,因为数据范围只有 0 < n , m < = 2 ∗ 1 0 4 0<n,m<=2*10^4 0<n,m<=2∗104
但是这道题只能拿24分…因为数据范围拉到了 2 e 5 2e5 2e5…
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<ext/rope>
#define pii pair<int,int>
#define N 200005
using namespace std;
using namespace __gnu_cxx;
rope<int>*his[N];
int find(int x,int root)
{
if(x==his[root]->at(x))return x;
int t=find(his[root]->at(x),root);
if(t!=his[root]->at(x))his[root]->replace(x,t);//不判的话会mle
return his[root]->at(x);
}
int main()
{
int n,m,last=0;
scanf("%d%d",&n,&m);
his[0]=new rope<int>;
his[0]->push_back(0);
for(int i=1;i<=n;i++)his[0]->push_back(i);
for(int i=1;i<=m;i++)
{
int op,a,b,u,v,k;
scanf("%d",&op);
if(op==1)
{
scanf("%d%d",&a,&b);
a^=last,b^=last;
u=find(a,i-1);
v=find(b,i-1);
his[i]=new rope<int>(*his[i-1]);
if(u==v)continue;
his[i]->replace(v,u);
}
if(op==2)
{
scanf("%d",&k);
k^=last;
his[i]=new rope<int>(*his[k]);
}
if(op==3)
{
scanf("%d%d",&a,&b);
a^=last,b^=last;
u=find(a,i-1);
v=find(b,i-1);
his[i]=new rope<int>(*his[i-1]);
printf("%d\n",u==v);
last=u==v;
}
}
}
4. luogu P6166 [IOI2012]scrivener【替代可持久化线段树】
题目链接:https://www.luogu.com.cn/problem/P6166
题意
设计支持如下 3 种操作:
1.T x:在文章末尾打下一个小写字母 x。(type 操作)
2.U x:撤销最后的x 次修改操作。(Undo 操作)
(注意Query 操作并不算修改操作)
3.Q x:询问当前文章中第x 个字母并输出。(Query 操作)
操作数n<=100000 在线算法
超简单的直接实现
#include<cstdio>
#include<cstring>
#include<cctype>
#include<iostream>
#include<algorithm>
#include<ext/rope>
using namespace std;
using namespace __gnu_cxx;
typedef long long ll;
const int N = 5000007, M = 5000007, INF = 0x3f3f3f3f;
ll read()
{
ll x = 0, f = 1;char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-')f = -1;ch = getchar();}
while(ch >= '0' && ch <= '9'){x = x * 10 + ch - '0';ch = getchar();}
return x * f;
}
int cnt;
rope<char> * now[N];
int n, m;
int num;
char op, str;
int main()
{
scanf("%d", &n);
now[0] = new rope<char>();
for(int i = 1; i <= n; ++ i){
cin >> op;
if(op == 'T'){
cin >> str;
cnt ++ ;//新版本
now[cnt] = new rope<char>(*now[cnt - 1]);
now[cnt]->push_back(str);
}
if(op == 'U'){
cin >> num;
cnt ++ ;
now[cnt] = new rope<char>(*now[cnt - num - 1]);//num个修改操作前的版本
}
if(op == 'P'){
cin >> num;
cout << now[cnt]->at(num) << endl;
//cout << now[cnt][num] << endl;//不能这么写
}
}
return 0;
}
某位大佬的另一种写法,用树状数组维护当前i是第几个修改操作,然后交上去0分…
#include<cstdio>
#include<cstring>
#include<cctype>
#include<iostream>
#include<algorithm>
#include<ext/rope>
using namespace std;
using namespace __gnu_cxx;
const int maxn=1e5+10;
rope<char> *his[maxn];
int n;
int d[maxn];
inline int lowbit(int x){ return x&-x; }
inline void updata(int x){ while(x<=n){ d[x]++; x+=lowbit(x); } }
inline int get(int x){ int res=0; while(x){ res+=d[x]; x-=lowbit(x); }return res; }
inline char getC(){ char ch=getchar(); while(!isalpha(ch))ch=getchar(); return ch; }
inline int getint()
{
int res=0; char ch,ok=0;
while(ch=getchar())
{
if(isdigit(ch)){ res*=10;res+=ch-'0';ok=1; }
else if(ok)break;
}
return res;
}
void deb(rope<char> s)
{
for(int i=0;i<s.length();i++)
cout<<s[i];puts("");
}
int main()
{
// freopen("type.in","r",stdin);
// freopen("type.out","w",stdout);
n=getint();
his[0]=new rope<char>();
for(int i=1;i<=n;i++)
{
his[i]=new rope<char>(*his[i-1]);
// deb(*his[i]);
char opt=getC();
if(opt=='T')
{
char x=getC();
his[i]->push_back(x);
updata(i);
}
else if(opt=='U')
{
updata(i);
int x=getint();
int l=1,r=i,mid,now=get(i);
while(l<r)
{
mid=(l+r)>>1;
if(now-get(mid)>x) l=mid+1;
else r=mid;
}
his[i]=his[l-1];
}
else if(opt=='Q')
{
int x=getint()-1;
putchar(his[i]->at(x));
putchar('\n');
} // deb(*his[i]);
}
return 0;
}
5. luogu AHOI2006文本编辑器editor【代替Splay】
题意
设计数据结构支持,插入删除反转字符串
分析:
由于rope的底层实现,insert,erase,get都是logn的
就是翻转不行,不是自己手写的打不了标记啊!!
怎么办?
答:同时维护一正一反两个rope……反转即交换两个子串……Orz……
区间循环位移?简单,拆成多个子串连起来就好了……
区间a变b b变c c变d …… z变a? 呃……维护26个rope?
区间和?滚蛋,那是线段树的活
区间kth?sorry,与数值有关的操作rope一概不支持……
5555 维修数列只能自己写了……
本题题解来源:https://www.cnblogs.com/vivym/p/3927949.html
#include <cstdio>
#include <ext/rope>
#include <iostream>
#include <algorithm>
using namespace std;
using namespace __gnu_cxx;
crope a,b,tmp;
char s[10];
int now,n,len,size;
char str[2000000],rstr[2000000];
int main()
{
scanf("%d",&n);
while(n--)
{
scanf("%s",s);
switch(s[0])
{
case 'M':
{
scanf("%d",&now);
break;
}
case 'P':
{
now--;
break;
}
case 'N':
{
now++;
break;
}
case 'G':
{
putchar(a[now]);
putchar('\n');
break;
}
case 'I':
{
scanf("%d",&size);
len=a.length();
for(int i=0;i<size;i++)
{
do
{
str[i]=getchar();
}
while(str[i]=='\n');
rstr[size-i-1]=str[i];
}
rstr[size]=str[size]='\0';
a.insert(now,str);
b.insert(len-now,rstr);
break;
}
case 'D':
{
scanf("%d",&size);
len=a.length();
a.erase(now,size);
b.erase(len-now-size,size);
break;
}
case 'R':
{
scanf("%d",&size);
len=a.length();
tmp=a.substr(now,size);
a=a.substr(0,now) + b.substr(len-now-size,size) + a.substr(now+size,len-now-size);
b=b.substr(0,len-now-size)+tmp+b.substr(len-now,now);
break;
}
}
}
return 0;
}
另一种写法+详细题解
https://wa-derfulanswer.blog.luogu.org/P4567
AC 代码 :
#include <cstdio>
#include <ext/rope>
const int N = 1<<22 + 7;
int n, k, cnt;
char now, inst[N], goal[N], bac[N];
struct readers{
char now;
bool state;
readers operator >>(int &goal){
goal = 0;
state = true;
while (now = getchar(), now < 48 || now > 57)state = now^45;
while (48 <= now && now <= 57){
goal = (goal<<1) + (goal<<3) + (now^48);
now = getchar();
}
if (!state)goal = -goal;
return *this;
}
}reader;
__gnu_cxx::rope<char> a, b, tmp;
int main(){
reader>>n;
while (n --){
/*bef:*/scanf("%s", inst);
// if (inst[0] == 10)goto bef;
// printf("inst:"), puts(inst);
if (inst[0] == 'I'/*"Insert"*/ || inst[0] == 'M'/* == "Move"*/ || inst[0] == 'D'/* == "Delete"*/ || inst[0] == 'R'/* == "Rotate"*/){
reader>>k;
if (inst[0] == 'M')cnt = k;
else if (inst[0] == 'I'){
register int length = a.size();
for (register int i = 0;i < k;i ++){
bac[k - i - 1] = goal[i] = getchar();
}
goal[k] = bac[k] = '\0';
a.insert(cnt, goal);
b.insert(length - cnt, bac);
}else if (inst[0] == 'D'){
register int length = a.size();
a.erase(cnt, k);
b.erase(length - cnt - k, k);
}else if (inst[0] == 'R'){
register int length = a.size();
tmp = a.substr(cnt, k);
a = a.substr(0, cnt) + b.substr(length - cnt - k, k) + a.substr(cnt + k, length - cnt - k);
b = b.substr(0, length - cnt - k) + tmp + b.substr(length - cnt, cnt);
}
}else if (inst[0] == 'P'/* == "Prev"*/)cnt --;
else if (inst[0] == 'N'/* == "Next"*/)cnt ++;
else if (inst[0] == 'G'/* == "Get"*/){
putchar(a[cnt]);
if (a[cnt] != 10)putchar(10);
}
}
return 0;
}
6. 总结
还是不太好用,数据大了容易MLE什么的,几道模板题都不能直接A,都是只能拿部分分,所以能不用就不用,如果遇见数据比较小的( 1 0 4 10^4 104 ~ 1 0 5 10^5 105非可持久化可以一试或者 1 0 4 10^4 104的可持久化数据结构可以一试)需要写可持久化的数据结构或者平衡树的基本操作什么的可以用rope试一下,但还是不要报有太大的幻想hhh。
也就是说只要不是卡的特别严格的题,都可以用rope试一下,我们可以想出思路之后先用rope实现一下,看看思路对不对,可以先交上去试试hhh,实在不行也可以用来对拍!
7. C++官方自带可持久化平衡树rope的3000行源码
将近三千行的源码hhh,放到这里太多了,我就放到一个新的博客里了。还是读一读源码会好一点
博客链接:https://fanfansann.blog.csdn.net/article/details/109608491
参考