题解 UVA12538 【自带版本控制功能的IDE Version Controlled IDE】

题目大意

维护一种数据结构,资磁三种操作。

1.在p位置插入一个字符串s

2.从p位置开始删除长度为c的字符串

3.输出第v个历史版本中从p位置开始的长度为c的字符串1≤n≤50000,所有字符串总长度小于等于10^6,输出字符串总长度小于等于20000

强制在线,每次输入中的数字都要减去你的所有输出中字母c的个数

思路 #1:可持久化FHQ Treap

(如果你懒得打一大长串的代码,就去看思路#2)

不难看出,这是一道可持久化FHQ Treap的模板题,就是在插入时要O(n)建树,要不会TLE

我们可以直接用一个递归函数build来解决

// s : 用来存储插入的字符串
// struct z[] : 可持久化Treap的本体 QwQ
// int newnode(char) : 建立一个新的节点

void build(int l, int r, int &rt) {
    if(l > r) return; // 边界
    int m = (l + r) >> 1; 
    rt = newnode(s[m]);
    build(l, m - 1, z[rt].ch[0]);
    build(m + 1, r, z[rt].ch[1]);
    update(rt);  // 更新信息
}

然后我们将根节点直接并到总树中去即可

一定要记住,我们要统计已经输出 'c' 的个数,然后把输入的数字减去这个已经输出的 'c' 的个数(强制在线嘛QwQ)

剩下的细节在代码的注释中写到:

code:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
#define go(i, j, n, k) for(int i = j; i <= n; i += k)
#define mn 50010
inline int read(){
    int x = 0, f = 1; char ch = getchar();
    while(ch > '9' || ch < '0') { if(ch == '-') f = -f; ch = getchar(); }
    while(ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
    return x * f;
}

char s[mn * 10];
struct tree {
    int ch[2], pri, sze;
    char w;
} z[mn * 100]; // 可持久化的数据结构嘛,数组要开大几十倍 
int n, m, cnt, rot[mn], xx, yy, zz, d, now;
// xx / yy / zz :辅助变量,记录分裂后子树的根节点
// d :记录已经输出的 'c' 的个数
// now :目前版本
 
inline void update(int rt) {
    z[rt].sze = 1;
    if(z[rt].ch[0])
        z[rt].sze += z[z[rt].ch[0]].sze;
    if(z[rt].ch[1])
        z[rt].sze += z[z[rt].ch[1]].sze;
}
// 更新sze(按解决的问题看还可以类似线段树的更新一样可以更新其他信息)
 
inline int newnode(char w = 0) {
    z[++cnt].w = w;
    z[cnt].pri = rand();
    z[cnt].sze = 1;
    return cnt;
}
// 建立新节点的函数

 
int merge(int x, int y) {
    if(!x || !y) return x + y; 
    int rt = newnode(); 
    if(z[x].pri < z[y].pri) {
        z[rt] = z[x];
        z[rt].ch[1] = merge(z[rt].ch[1], y);
    } else {
        z[rt] = z[y];
        z[rt].ch[0] = merge(x, z[rt].ch[0]);
    }
    update(rt);
    return rt;
}
// 合并操作(模板 

void split(int rt, int k, int &x, int &y) {
    if(!rt) x = y = 0;
    else {
        if(k <= z[z[rt].ch[0]].sze) {
            z[y = newnode()] = z[rt];
            split(z[y].ch[0], k, x, z[y].ch[0]);
            update(y);
        } else {
            z[x = newnode()] = z[rt];
            split(z[x].ch[1], k - z[z[rt].ch[0]].sze - 1, z[x].ch[1], y);
            update(x);
        }
    }
}
// 分裂操作(模板 

inline int findkth(int rt, int k) {
    while(1119) {
        if(k <= z[z[rt].ch[0]].sze)
            rt = z[rt].ch[0];
        else {
            if(z[rt].ch[0]) k -= z[z[rt].ch[0]].sze;
            if(!--k) return rt;
            rt = z[rt].ch[1];
        }
    }
}
// 查询第 k 值(模板,这里好像没有用 

void build(int l, int r, int &rt) {
    if(l > r) return;
    int m = (l + r) >> 1;
    rt = newnode(s[m]);
    build(l, m - 1, z[rt].ch[0]);
    build(m + 1, r, z[rt].ch[1]);
    update(rt); 
}
// 建树操作
 
inline void ins(int &rt, int last, int pos) {
    // rt : 更新的版本根节点
    // last : 旧版本的根节点
    // pos : 要插入的位置 
    xx = yy = zz = 0;
    scanf("%s", s + 1);
    // 字符串的输入让我放在这个地方了哦QwQ 
    int len = strlen(s + 1); 
    split(last, pos, xx, zz);
    // 注意这个地方要用旧版本的根节点进行 split 操作 
    build(1, len, yy);
    // 建树操作 
    rt = merge(merge(xx, yy), zz);
    // 重新合并 
}
// 插入操作 

inline void del(int &rt, int last, int pos, int len) {
    // rt : 更新的版本根节点
    // last : 旧版本的根节点
    // pos : 要删除的位置 
    // len : 要删除的长度 
    xx = yy = zz = 0;
    // 记得清零,要不影响下次调用 
    split(last, pos - 1, xx, yy);
    split(yy, len, yy, zz);
    // 分成三组,然后把前后两组合并(中间的是被删除的 
    rt = merge(xx, zz);
}
// 删除操作 

void print(int rt) {
    // rt : 当前节点 
    if(!rt) return;
    print(z[rt].ch[0]);
    putchar(z[rt].w);
    if(z[rt].w == 'c') ++d;
    // 记得统计 'c' 啊 
    print(z[rt].ch[1]);
}
// 遍历二叉树输出答案 

void print(int rt, int pos, int len) {
    // rt : 更新的版本根节点
    // pos : 要输出的起始位置 
    // len : 要输出的长度 
    xx = yy = zz = 0;
    // 记得清零,要不影响下次调用 
    split(rt, pos - 1, xx, yy);
    split(yy, len, yy, zz);
    // 分离出我们要输出的子树 
    print(yy);
    putchar('\n');
}

inline void solve() {
    n = read();
    go(i, 1, n, 1) {
        xx = yy = zz = 0;
        int s = read();
        if(s == 1) {
            int p = read() - d; 
            // 注意要 - d!(看题目要求啊QAQ 
            ins(rot[now + 1], rot[now], p);
            ++now;
            // 毕竟更新过一次,所以版本数 + 1 
        } else if(s == 2) {
            int p = read() - d, l = read() - d;
            // 注意要 - d!!(看题目要求啊QAQ 
            del(rot[now + 1], rot[now], p, l);
            ++now;
            // 毕竟更新过一次,所以版本数 + 1 
        } else {
            int ver = read() - d, p = read() - d, l = read() - d;
            // 注意要 - d!!!(看题目要求啊QAQ 
            print(rot[ver], p, l);
        }
    }
}
int main(){
//  init();
    solve();
    return 0;
}

思路 #2:STL —— rope

直接使用STL中的rope,这个代码太易懂了,我就不细讲了,免的写平衡树了(就是稍微慢点)

code

#include <iostream>
#include <cstdio>
#include <ext/rope>
#include <algorithm>
using namespace std;
#define go(i, j, n, k) for(int i = j; i <= n; i += k)
inline int read(){
    int x = 0, f = 1; char ch = getchar();
    while(ch > '9' || ch < '0') { if(ch == '-') f = -f; ch = getchar(); }
    while(ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
    return x * f;
}
using namespace __gnu_cxx; 
crope z[50010], r, str;
char a[222];
int n, p, d, c, v, now;
inline void solve() {
    n = read();
    go(x, 1, n, 1) {
        int s = read();
        if(s == 1) {
            p = read() - d;
            scanf("%s", a);
            r.insert(p, a);
            z[++now] = r;
        } else if(s == 2) {
            p = read() - d, c = read() - d;
            r.erase(p - 1, c);
            z[++now] = r;
        } else if(s == 3) {
            v = read() - d, p = read() - d, c = read() - d; 
            str = z[v].substr(p - 1, c);
            d += count(str.begin(), str.end(), 'c');
            cout << str << "\n";
        }
    }
}
int main(){
//  init();
    solve();
    return 0;
}

希望可以帮到初学可持久化FHQ Treap和STL rope的同学QwQ

猜你喜欢

转载自www.cnblogs.com/yizimi/p/10391678.html
IDE