[SPOJ]LCS2
和LCS那题很像,但是现在有十个串了……
还是考虑构建一个串的SAM。然后考虑SAM上每个点,剩余若干个串都可能会覆盖一下这个点,我们要取最短的那个。
然后我们就直接把其他几个串都在SAM上跑一遍(方法类似于LCS)就行了,并且注意每个点对自己的祖先也是有贡献的。
代码:
#include <cstdio> #include <cstring> #include <cstdlib> #include <cctype> #include <algorithm> #include <utility> #include <queue> #include <unordered_set> const int maxn = 100005; const int INF = 0x3f3f3f3f; const int maxno = maxn << 1; struct Node { Node *fa; int len; int ans[12]; Node *ch[26]; Node *lc, *rb; }; Node pool[maxno]; Node *rt, *last; Node *cnt; void init_pool() { rt = last = cnt = pool; rt -> fa = NULL; rt -> len = 0; rt -> ans[0] = 0; // memset(rt -> ans, 0, sizeof(rt -> ans)); } Node *alloc_node(int len = 0, Node *fa = NULL) { Node *ret = ++ cnt; ret -> len = len; ret -> fa = fa; // memset(ret -> ans, 0, sizeof(ret -> ans)); ret -> ans[0] = len; return ret; } int idx(char c) { return c - 'a'; } void insert(char cc) { int c = idx(cc); Node *np = alloc_node(last -> len + 1); Node *p = last; while(p != NULL && p -> ch[c] == NULL) { p -> ch[c] = np; p = p -> fa; } if(!p) { np -> fa = rt; } else { Node *q = p -> ch[c]; if(q -> len == p -> len + 1) { np -> fa = q; } else { Node *nq = alloc_node(p -> len + 1, q -> fa); memcpy(nq -> ch, q -> ch, sizeof(q -> ch)); q -> fa = np -> fa = nq; while(p != NULL && p -> ch[c] == q) { p -> ch[c] = nq; p = p -> fa; } } } last = np; } void dfs(int id, Node *u) { for(Node *c = u -> lc; c; c = c -> rb) { dfs(id, c); u -> ans[id] = std::max(u -> ans[id], c -> ans[id]); } } void run(int id, char *S) { int n = strlen(S); Node *u = rt; int len = 0; for(int i = 0; i < n; i ++) { int c = idx(S[i]); if(u -> ch[c]) { len ++; u = u -> ch[c]; } else { Node *p = u; while(p != NULL && p -> ch[c] == NULL) p = p -> fa; if(p == NULL) { len = 0; u = rt; } else { len = p -> len + 1; u = p -> ch[c]; } } u -> ans[id] = std::max(u -> ans[id], len); } dfs(id, rt); } int tot = 0; int dfs_2(Node *u) { int ans = *std::min_element(u -> ans, u -> ans + tot + 1); for(Node *c = u -> lc; c; c = c -> rb) { ans = std::max(ans, dfs_2(c)); } return ans; } int main() { static char buf[maxn]; scanf("%s", buf); init_pool(); int n = strlen(buf); for(int i = 0; i < n; i ++) { insert(buf[i]); } for(int i = 1; i <= (cnt - pool); i ++) { Node *u = pool + i; Node *f = u -> fa; u -> rb = f -> lc; f -> lc = u; } while(scanf("%s", buf) == 1) { #ifdef LOCAL if(buf[0] == '-') break; #endif run(++ tot, buf); // memset(buf, 0, sizeof(buf)); } printf("%d\n", dfs_2(rt)); return 0; }
[SPOJ]LCS
震惊!SAM与AC自动机间的神秘关系,竟然是为了……
考虑到是子串嘛,我们建立\(A\)的后缀自动机,考虑把\(B\)放在上面进行匹配。然后考虑当前节点下面再加一个字符,如果有这个字符的转移那么直接走过去即可。如果没有呢?
观察到SAM上的父亲其实就是一个被儿子包含了的子串,所以这一步走不下去,可以考虑去找父亲解决。所以找到最低的有该转移的祖先,转移过去即可。
代码:
#include <cstdio> #include <cstring> #include <cstdlib> #include <cctype> #include <algorithm> #include <utility> #include <queue> const int maxn = 250005; const int maxsiz = 30000005; char buf[maxsiz]; char *cur = buf; void *alloc(size_t size) { if(cur - buf + size > maxsiz) { return malloc(size); } else { void *ret = (void*)cur; cur += size; return ret; } } int idx(char c) { return c - 'a'; } struct Node { Node *fa; int len; Node *ch[26]; }; Node *alloc_node(int len = 0, Node *fa = NULL) { Node *ret = (Node*)alloc(sizeof(Node)); ret -> len = len; ret -> fa = fa; memset(ret -> ch, 0, sizeof(ret -> ch)); return ret; } Node *rt, *last; void init_sam() { rt = alloc_node(); last = rt; } void extend(char c) { int i = idx(c); Node *np = alloc_node(last -> len + 1); Node *p = last; while(p != NULL && p -> ch[i] == NULL) { p -> ch[i] = np; p = p -> fa; } if(p == NULL) { np -> fa = rt; } else { Node *q = p -> ch[i]; if(q -> len == p -> len + 1) { np -> fa = q; } else { Node *nq = alloc_node(p -> len + 1, q -> fa); memcpy(nq -> ch, q -> ch, sizeof(q -> ch)); np -> fa = q -> fa = nq; while(p != NULL && p -> ch[i] == q) { p -> ch[i] = nq; p = p -> fa; } } } last = np; } char A[maxn], B[maxn]; int search() { int n = strlen(B); Node *u = rt; int ans = 0, len = 0; for(int i = 0; i < n; i ++) { int c = idx(B[i]); if(u -> ch[c]) { u = u -> ch[c]; len ++; } else { while(u != NULL && u -> ch[c] == NULL) u = u -> fa; if(u == NULL) { u = rt; len = 0; } else { len = u -> len + 1; u = u -> ch[c]; } } ans = std::max(ans, len); } return ans; } int main() { scanf("%s%s", A, B); int n = strlen(A); init_sam(); for(int i = 0; i < n; i ++) { extend(A[i]); } printf("%d\n", search()); return 0; }
[SPOJ]NSUBSTR
第一次做了一道有一定难度的SAM题……
考虑每个SAM上的结点\(x\),他的子串表示范围一定是一个区间\([Min(x), Max(x)]\),然后他的\(Right(x)\)的大小就是该点所代表的每个子串的出现次数。
那么考虑怎么搞出这个\(|Right(x)|\)。我们把每个“实点”(就是在SAM每一次插入后成为\(last\)的结点)的点权钦定为1,其他搞成0。然后每个点的\(|Right(x)|\)就是\(x\)在Right树上的子树的点权和。
然后接下来看上去可以直接做……但是区间chkmax这种黑暗操作,是不是要涉及毒瘤数据结构?
答案是否定的。任何短的子串都是某一个长子串的子串,所以说我们只需要在\(Max(x)\)那个地方chkmax,然后搞个后缀最大值即可。
代码:
#include <cstdio> #include <cstring> #include <cstdlib> #include <cctype> #include <algorithm> #include <utility> #include <queue> const int maxn = 250005; const int sigma_siz = 26; struct Node { int maxl, rs; Node *fa; Node *ch[sigma_siz]; }; Node pool[maxn << 1]; Node *tot = pool; Node *rt = tot, *last = tot; void extend(int c) { Node *np = ++ tot; np -> maxl = last -> maxl + 1; np -> rs = 1; Node *p = last; while(p != NULL && p -> ch[c] == NULL) { p -> ch[c] = np; p = p -> fa; } if(p == NULL) { np -> fa = rt; } else { Node *q = p -> ch[c]; if(q -> maxl == p -> maxl + 1) { np -> fa = q; } else { Node *nq = ++ tot; nq -> maxl = p -> maxl + 1; nq -> fa = q -> fa; memcpy(nq -> ch, q -> ch, sizeof(q -> ch)); q -> fa = np -> fa = nq; while(p != NULL && p -> ch[c] == q) { p -> ch[c] = nq; p = p -> fa; } } } last = np; } int first[maxn << 1]; int next[maxn << 2], to[maxn << 2]; int gcnt = 0; void add_edge(int u, int v) { gcnt ++; next[gcnt] = first[u]; first[u] = gcnt; to[gcnt] = v; } int len[maxn << 1], siz[maxn << 1]; int F[maxn]; void dfs(int x) { for(int i = first[x]; i; i = next[i]) { int v = to[i]; dfs(v); siz[x] += siz[v]; } F[len[x]] = std::max(F[len[x]], siz[x]); } int main() { static char S[maxn]; scanf("%s", S); int n = strlen(S); for(int i = 0; i < n; i ++) { extend(S[i] - 'a'); } for(int i = 0; i <= (tot - pool); i ++) { Node *p = pool + i; len[i] = p -> maxl; siz[i] = p -> rs; if(p -> fa) { int fa = (p -> fa) - pool; add_edge(fa, i); } } dfs(0); for(int i = n - 1; i >= 0; i --) { F[i] = std::max(F[i], F[i + 1]); } for(int i = 1; i <= n; i ++) { printf("%d\n", F[i]); } return 0; }
[BZOJ 2588]Count on a tree
泥萌感觉我还能说什么……
这题就是DFS序套主席树,顺便带上倍增LCA,但有两大坑点:
- 存放数据不一定要用long long,但输入必须要。
- 输出数据蛋疼,最后一行行末没回车。
代码: