【luogu P4770】[NOI2018] 你的名字(SAM)(线段树合并)

[NOI2018] 你的名字

题目链接:luogu P4770

题目大意

给你一个字符串 S,然后每次询问给你一个字符串 T 和 l,r。
要你求 T 中有多少个子串不是 S[l]~S[r] 组成的字符串的子串。

思路

首先我们看到很多的部分分是 l=1,r=|S|
那我们先想想怎么搞这个,也就是求 T 中有多少个子串不是 S 的子串。

首先考虑反过来求有多少个串同时是 S,T 的子串。
然后我们考虑把 T 在 S 的 SAM 上面跑,然后每个位置有匹配长度 len,然后对于 T 的 SAM 的每个点我们记录一个 pli 表示这个点是字符串哪个前缀跑出来的点,然后跑到这个位置的 len 就是能匹配的长度 ansi
然后答案就是枚举 T 的每个点(1 号点除外),贡献是 max(0,lenimax(lenfai,anspli))(这个 max(0) 是防止 anspli 太大)

那复杂度为 O(|T|) 可以接受。

接着考虑 l,r 随便的情况。
那我们考虑会有什么问题,就是求这个 ansi 的时候,你跑出来的点不一定行。
你要这个点是在 Slr 的 SAM 上才可以,亦或者说要这个点的 endpos 类里面有 lr 的信息。
那要怎么判呢,我们考虑维护 S 的 SAM 中一个点的 endpos。
怎么维护呢,我们可以用动态开点线段树,然后父亲的 endpos 是自己并上儿子的 endpos,可以用线段树合并来解决。

代码

#include<cstdio> #include<cstring> #include<iostream> #define ll long long using namespace std; const int N = 5e5 + 10; char s[N], t[N]; int n, m, q, L, R, rt[N << 1], ans[N << 1]; struct XD_tree { int tot, val[N << 6], ls[N << 6], rs[N << 6]; int insert(int x, int l, int r, int pl) { int now = ++tot; val[now] = val[x]; ls[now] = ls[x]; rs[now] = rs[x]; val[now] = max(val[now], pl); if (l == r) return now; int mid = (l + r) >> 1; if (pl <= mid) ls[now] = insert(ls[now], l, mid, pl); else rs[now] = insert(rs[now], mid + 1, r, pl); return now; } int query(int now, int l, int r, int L, int R) { if (!now) return 0; if (L <= l && r <= R) return val[now]; int mid = (l + r) >> 1, re = 0; if (L <= mid) re = max(re, query(ls[now], l, mid, L, R)); if (mid < R) re = max(re, query(rs[now], mid + 1, r, L, R)); return re; } int merge(int x, int y) { if (!x || !y) return x + y; int re = ++tot; val[re] = max(val[x], val[y]); ls[re] = merge(ls[x], ls[y]); rs[re] = merge(rs[x], rs[y]); return re; } }TT; struct SAM { int lst, tot; struct node { int son[26], len, fa, pl; }d[N << 1]; void Init() { lst = tot = 1; memset(d[1].son, 0, sizeof(d[1].son)); } int insert(char c, int pla) { int p = lst, np = ++tot; lst = np; memset(d[np].son, 0, sizeof(d[np].son)); d[np].len = d[p].len + 1; d[np].pl = pla; for (; p && !d[p].son[c]; p = d[p].fa) d[p].son[c] = np; if (!p) d[np].fa = 1; else { int q = d[p].son[c]; if (d[q].len == d[p].len + 1) d[np].fa = q; else { int nq = ++tot; d[nq] = d[q]; d[nq].len = d[p].len + 1; d[np].fa = d[q].fa = nq; for (; p && d[p].son[c] == q; p = d[p].fa) d[p].son[c] = nq; } } return lst; } void build() { static int tmp[N], xl[N << 1]; for (int i = 1; i <= n; i++) tmp[i] = 0; for (int i = 1; i <= tot; i++) tmp[d[i].len]++; for (int i = 1; i <= n; i++) tmp[i] += tmp[i - 1]; for (int i = 1; i <= tot; i++) xl[tmp[d[i].len]--] = i; for (int i = tot; i >= 1; i--) {//线段树合并来求SAM每个点的endpos int x = xl[i]; rt[d[x].fa] = TT.merge(rt[d[x].fa], rt[x]); } } void Run(int &now, int &sz, int c) { while (now) { if (d[now].son[c]) { int x = TT.query(rt[d[now].son[c]], 1, n, 1, R) - L + 1; if (d[d[now].fa].len < x) {//不仅要跳到有这个儿子,还需要这个儿子的位置中有 L~R(前面这个比较的要小于这样减的时候【DP】才会是正的) now = d[now].son[c]; sz = min(sz + 1, x); return ; } } now = d[now].fa; sz = d[now].len; } now = 1; sz = 0; } ll work() { ll re = 0; for (int i = 2; i <= tot; i++) re += max(d[i].len - max(d[d[i].fa].len, ans[d[i].pl]), 0); return re; } }S, T; int main() { scanf("%s", s + 1); n = strlen(s + 1); S.Init(); for (int i = 1; i <= n; i++) { int pl = S.insert(s[i] - 'a', 0); rt[pl] = TT.insert(rt[pl], 1, n, i); } S.build(); scanf("%d", &q); while (q--) { scanf("%s", t + 1); m = strlen(t + 1); scanf("%d %d", &L, &R); T.Init(); for (int i = 1; i <= m; i++) T.insert(t[i] - 'a', i); int now = 1, sz = 0; for (int i = 1; i <= m; i++) { S.Run(now, sz, t[i] - 'a'); ans[i] = sz; } printf("%lld\n", T.work()); } return 0; }

__EOF__

本文作者あおいSakura
本文链接https://www.cnblogs.com/Sakura-TJH/p/luogu_P4770.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   あおいSakura  阅读(41)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示