题目大意:
就是现在初始有一个字符串, 然后接下来有m (m <= 10W) 次操作, 每次操作可能是在现有字符串后面加上一段字符, 或者是删掉一段字符, 或者是询问当前字符串中长度为len的所有字串中字典序最小的, 还要讲长度不够的后缀也考虑在内, 并且在有多个相同的时候输出下标最小的
大致思路:
首先用后缀自动机的话对于添加操作和询问操作都很容易解决, 但是对于删除操作就不太好办了
对于删除操作需要对于SAM中的每个节点都添加一个del指针, 表示是否已经被删除, 然后由于每次添加新字符的时候最多只会添加2个节点, 将有同一次插入字符新生成的结点的del指针指向同一个bool的域, 那么删除操作的时候只需要修改这个域中对应的bool值同时删除了这些结点
注意这里的删除并不是真的将这个点从SAM中去掉了, 其实并没有, 只是将他们标记为删除了而已, SAM中点的个数没有减少, 并且删除操作之后要将last指针指向最后一个字符新建的np结点, , 也就是最后的right集合为{L}(L为当前长度)的那个结点
这样在每次询问的时候只要不沿着已经删除的点走就可以了, 细节见代码和代码注释吧
代码如下:
Result : Accepted Memory : 36124 KB Time : 608 ms
/* * Author: Gatevin * Created Time: 2015/4/27 11:24:39 * File Name: Rin_Tohsaka.cpp */ #include<iostream> #include<sstream> #include<fstream> #include<vector> #include<list> #include<deque> #include<queue> #include<stack> #include<map> #include<set> #include<bitset> #include<algorithm> #include<cstdio> #include<cstdlib> #include<cstring> #include<cctype> #include<cmath> #include<ctime> #include<iomanip> using namespace std; const double eps(1e-8); typedef long long lint; #define foreach(e, x) for(__typeof(x.begin()) e = x.begin(); e != x.end(); ++e) #define SHOW_MEMORY(x) cout<<sizeof(x)/(1024*1024.)<<"MB"<<endl #define maxn 400010 #define maxm 200010 bool Delete[maxm]; int D;//Delete域的个数 char s[maxm];//字符串 int L;//当前的有效字符串的长度 int pos[maxm]; //表示当前的串的第i个字符在sam中对应的结点np是nodePool[pos[i]], 由于np与nq共享一个Delete域记住这个就够了 int col;//给suf染色的颜色 struct Suffix_Automation { struct State { State *par; State *go[26]; bool* del;//指示是否被删除 int right, mi, cnt, val, leftmost; int suf; void init(int _val = 0) { par = 0, val = _val, right = mi = cnt = 0, leftmost = 1e9, suf = 0; memset(go, 0, sizeof(go)); } int calc() { if(par == 0) return 0; else return val - par->val; } }; State *last, *cur, *root; State nodePool[maxn]; State* newState(int val = 0) { cur->init(val); return cur++; } void init() { cur = nodePool; root = newState(); root->del = &Delete[D++]; last = root; } void extend(int w, int l) { State *p = last; State *np = newState(p->val + 1); np->leftmost = l; //事实上考虑到right集合的性质以及np状态中包含的最长串可知这一点, 求leftmost并不需要topo排序 pos[l] = cur - nodePool - 1; np->del = &Delete[D++]; *(np->del) = false; np->right = 1; while(p && (p->go[w] == 0 || *(p->go[w]->del))) { p->go[w] = np; p = p->par; } if(p == 0) { np->par = root; } else { State *q = p->go[w]; if(p->val + 1 == q->val) { np->par = q; } else { State *nq = newState(p->val + 1); memcpy(nq->go, q->go, sizeof(q->go)); nq->del = q->del; nq->par = q->par; q->par = nq; np->par = nq; nq->leftmost = q->leftmost;//nq作为新建的结点介于q和q-par之间, 儿子只有q和np, right集合中的最小肯定是q中的最小 while(p && p->go[w] == q)//q肯定是没有被del的 { p->go[w] = nq; p = p->par; } } } last = np; } void Del(int len) { for(int i = L - len; i < L; i++) *(nodePool[pos[i]].del) = true;//这些点标记为删除 L -= len;//字符总长度减少 last = &nodePool[pos[L - 1]];//标记添加的最末尾字符对应的np位置 return; } void Die()//给right集合中包含最后的L的染色, 方便solve的递归求解 { State *e = last; while(e->par && e != root) e->suf = col, e = e->par; return; } bool solve(State *now, int nowlen, int len) { if(nowlen == len) { printf("%d\n", now->leftmost - (len - 1) + 1); return true; } if(now->suf == col)//是后缀点 { printf("%d\n", L - nowlen + 1); return true; } for(int i = 0; i < 26; i++) if(now->go[i] && !*(now->go[i]->del)) if(solve(now->go[i], nowlen + 1, len)) return true; return true; } }; Suffix_Automation sam; int main() { while(scanf("%s", s) != EOF) { int n = strlen(s); sam.init(); D = 0, L = 0, col = 0; memset(Delete, 0, sizeof(Delete)); for(int i = 0; i < n; i++) sam.extend(s[i] - 'a', L++); int m, k; scanf("%d", &m); while(m--) { scanf("%d", &k); switch(k) { case 1: scanf("%s", s); n = strlen(s); for(int i = 0; i < n; i++) sam.extend(s[i] - 'a', L++); break; case 2: scanf("%d", &n), col++, sam.Die(), sam.solve(sam.root, 0, n); break; case 3: scanf("%d", &n), sam.Del(n); break; } } } return 0; }