[LibreOJ 2137][ZJOI2015]诸神眷顾的幻想乡
复习SAM了呢~
那个度数为1的点至多有20个的条件非常神奇……让我们想想怎么用。
我们发现,(钦定根后)在树上有一条路径是弯的这种情况非常不好统计,但如果是直着下来就很好说(把所有从根到一个点的路径扔到SAM里,然后是经典题)。但是,任何一条路一定会在某个度数为1的点为根的情况下是直的(可以意会一下吧(逃
然后我们从那20个点每个点当根DFS一遍,把搞出来的每一条从根开始的路径放前缀Trie里。然后对前缀Trie构一个SAM就行啦~
代码:
#include <cstdio> #include <cstring> #include <cstdlib> #include <cctype> #include <algorithm> #include <utility> #include <queue> #include <vector> const int BUFSIZE = 128 * 1024 * 1024; char buf[BUFSIZE]; void *alloc(size_t size) { static char *cur = buf; if(cur - buf + size > BUFSIZE) { return malloc(size); } else { char *ret = cur; cur += size; return ret; } } const int maxn = 100005; std::vector<int> G[maxn]; int deg[maxn]; int ssiz; struct TNode { TNode *ch[10]; }; TNode *alloc_tn() { auto ret = (TNode*)alloc(sizeof(TNode)); memset(ret -> ch, 0, sizeof(ret -> ch)); return ret; } TNode *step(TNode *o, int c) { if(!(o -> ch[c])) { o -> ch[c] = alloc_tn(); } return (o -> ch[c]); } TNode *trt; int col[maxn]; void dfs(int x, int fa, TNode *last) { TNode *np = step(last, col[x]); for(auto v : G[x]) { if(v != fa) { dfs(v, x, np); } } } struct Node { int len; Node *fa; Node *ch[10]; }; std::vector<Node*> pool; 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)); pool.push_back(ret); return ret; } Node *rt; Node *extend(int c, Node *last) { 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 == NULL) { 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; } } } return np; } void dfs_2(TNode *o, Node *last) { for(int i = 0; i < ssiz; i ++) { TNode *v = o -> ch[i]; if(!v) continue; Node *np = extend(i, last); dfs_2(v, np); } } using ll = long long; int main() { int n; scanf("%d%d", &n, &ssiz); for(int i = 1; i <= n; i ++) { scanf("%d", &col[i]); } trt = alloc_tn(); for(int i = 1; i <= n - 1; i ++) { int u, v; scanf("%d%d", &u, &v); G[u].push_back(v); G[v].push_back(u); deg[u] ++; deg[v] ++; } for(int i = 1; i <= n; i ++) { if(deg[i] == 1) { dfs(i, 0, trt); } } rt = alloc_node(); dfs_2(trt, rt); ll ans = 0; for(auto p : pool) { if(p -> fa) { ans += p -> len - p -> fa -> len; } } printf("%lld\n", ans); return 0; }
[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; }
[LibreOJ 2102][TJOI2015]弦论
xjbYY了一下竟然就艹过去了……
如果说是我们知道了每个点出发能得到的串有多少个,我们就能用类似树上求\(k\)大的求法来搞了。问题来了,怎么知道这个?
我先说一下不重复的情况吧,首先到达这个点就有一种串了,然后我们加上他的出边(注意是转移边!)指向的点的值就行了。重复的情况我们考虑一下\(|Right(x)|\)就能搞出来了
代码:
#include <cstdio> #include <cstring> #include <cstdlib> #include <cctype> #include <utility> #include <queue> #include <algorithm> #include <set> const int maxn = 500005; const int maxno = maxn * 4; const int bufsiz = 50 * 1024 * 1024; typedef long long ll; int fa[maxno], ch[maxno][26]; int len[maxno]; ll ans[maxno][2], val[maxno]; int rt, last; int cur; inline void init_sam() { rt = last = 1; cur = 1; ans[1][0] = ans[1][1] = -1LL; } inline int alloc_node(int l = 0, int f = 0) { int ret = ++ cur; len[ret] = l; fa[ret] = f; ans[ret][0] = ans[ret][1] = -1LL; return ret; } inline int idx(char c) { return c - 'a'; } inline void extend(char cx) { int c = idx(cx); int np = alloc_node(len[last] + 1); val[np] = 1LL; int p = last; while(p && !(ch[p][c])) ch[p][c] = np, p = fa[p]; if(!p) { fa[np] = rt; } else { int q = ch[p][c]; if(len[q] == len[p] + 1) { fa[np] = q; } else { int nq = alloc_node(len[p] + 1, fa[q]); memcpy(ch[nq], ch[q], sizeof(ch[q])); fa[q] = fa[np] = nq; while(p && ch[p][c] == q) ch[p][c] = nq, p = fa[p]; } } last = np; } int lc[maxno], rb[maxno]; inline void add_edge(int c, int f) { rb[c] = lc[f]; lc[f] = c; } void dfs_1(int x) { for(int i = lc[x]; i; i = rb[i]) { dfs_1(i); val[x] += val[i]; } } void dfs_2(int x) { if(ans[x][0] != -1LL) return; ans[x][0] = 1LL; ans[x][1] = val[x]; for(int c = 0; c < 26; c ++) { int v = ch[x][c]; if(v) { dfs_2(v); ans[x][0] += ans[v][0]; ans[x][1] += ans[v][1]; } } } inline void process() { for(int i = 2; i <= cur; i ++) { add_edge(i, fa[i]); } dfs_1(1); dfs_2(1); #ifdef LOCAL for(int i = 1; i <= cur; i ++) { printf("ans[%d] : (%lld, %lld)\n", i, ans[i][0], ans[i][1]); } #endif } char ret[maxno]; void search(ll k, int typ) { static ll val_0[maxno]; for(int i = 1; i <= cur; i ++) { val_0[i] = 1; } ll *self[2] = {val_0, val}; k += self[typ][1]; if(k > ans[1][typ]) { ret[0] = '-', ret[1] = '1'; return; } int cnt = 0; int u = 1; while(k > self[typ][u]) { int used = 0; k -= self[typ][u]; for(int c = 0; c < 26; c ++) { int v = ch[u][c]; if(!v) continue; used ++; if(k > ans[v][typ]) { k -= ans[v][typ]; } else { ret[cnt ++] = c + 'a'; #ifdef LOCAL printf("Towardsing %d with %c\n", v, c + 'a'); printf("k : %lld\n", k); #endif u = v; break; } } // if(used == 0) break; } } int main() { static char S[maxn]; scanf("%s", S); int n = strlen(S); int typ; ll k; scanf("%d%lld", &typ, &k); init_sam(); for(int i = 0; i < n; i ++) { extend(S[i]); } process(); search(k, typ); puts(ret); 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; }
[LibreOJ 2033][SDOI2016]生成魔咒
辣鸡爆炸OJ又被卡了说好的修bug呢说好的新OJ呢哼我去loj交了
本题的正解是SA,但很显然可以用SAM搞过去(逃
首先字符集过大?我们可以考虑开map解决转移的问题。
然后思考一个点加入后对答案会有什么影响。一个点的对答案的贡献,就是他自己的最大表示范围减去他父亲的。因为更小的长度在之前都已经被表示过了。即使之后这个点被拆成了两个,那这两个点对答案的总贡献是不会变的。所以每次加完点,把贡献加到答案里即可。
代码:
#include <cstdio> #include <cstdlib> #include <cstring> #include <algorithm> #include <map> typedef long long ll; #define REP(i, l, r) for(int i = (l); i <= (r); i ++) struct Node { Node *fa; std::map<int, Node*> *ch; int len; Node() { fa = NULL; ch = new std::map<int, Node*>(); } Node(int l) { fa = NULL; ch = new std::map<int, Node*>(); len = l; } ~Node() { delete ch; } }; Node *rt, *last; void init() { rt = new Node(0); // rt -> tl = 1; last = rt; } ll ans = 0; void insert(int c) { static int clk = 1; Node *np = new Node(); np -> len = last -> len + 1; Node *p = last; while(p != NULL && p -> ch -> count(c) == 0) { p -> ch -> insert(std::make_pair(c, np)); p = p -> fa; } if(p == NULL) { np -> fa = rt; } else { Node *q = (*(p -> ch -> find(c))).second; if(q -> len == p -> len + 1) { np -> fa = q; } else { Node *nq = new Node(p -> len + 1); nq -> fa = q -> fa; nq -> ch -> insert(q -> ch -> begin(), q -> ch -> end()); // nq -> ch -> insert(std::make_pair(c, np)); np -> fa = q -> fa = nq; while(p != NULL && ((*(p -> ch -> find(c))).second) == q) { p -> ch -> erase(p -> ch -> find(c)); p -> ch -> insert(std::make_pair(c, nq)); p = p -> fa; } } } last = np; ans += np -> len - np -> fa -> len; } int main() { int n; scanf("%d", &n); init(); REP(i, 1, n) { int c; scanf("%d", &c); insert(c); printf("%lld\n", ans); } 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; }