【NOI2018】你的名字(SAM & 线段树合并)
Description

Hint

Solution
不妨先讨论一下无区间限制的做法。
首先“子串”可以理解为“前缀的后缀”,因此我们定义一个 \(\lim(i)\),表示 \(T\) 的一个前缀 \(T[1\cdots i]\) 中,选取一个最长后缀,使得这个后缀在 \(S\) 中出现过。\(\lim(i)\) 就是这个最长后缀的长度。
其实与朴素的 SAM 求最长公共子串有点相似,这里主要是求 本质不同的公共子串的个数。
我们对 \(S\) 建 SAM,然后把 \(T\) 放到 \(S\) 上跑。如果存在转移,那么直接走,当前匹配长度加一;反之就退而求其次。
计算出 \(\lim\) 后,我们发现,对于 \(T\) 的一个前缀的所有后缀,长度不超过 \(\lim\) 的都在主串中出现过。
因为需要去重,所以对询问串 \(T\) 也需要建 SAM。于是不难写出答案的计算公式: \(\text{ans} = \sum\limits_{x\in Q(\text{SAM of } T)} \max(\text{len}(x) - \max(\text{len}(\text{len}(x), \lim(\text{len}(\text{dir}(x)))), 0)\)
简单解释一下:对于 \(T\) 的 SAM 中的结点 \(x\),原来是有 \(\text{len}(x) - \text{len}(\text{link}(x))\) 的贡献的,然而我们多了一个 \(\lim\) 的限制,于是我们将减数取最大值。而 \(\text{dir}\) 表示当前结点在构造 SAM 时,拆点是从哪个点拆出来的。如果并不是拆点新建的点则 \(\text{dir}(x) = x\)。实际上,\(\text{len}(\text{dir}(x))\) 即为 第一次出现的结尾位置。
接下来讨论如何应付区间限制。
这就需要维护 \(\text{end-pos}\) 集合。那么在转移前还需判断,下一个结点是否在 \(S[l + \text{len}\cdots r]\) 内。(\(\text{len}\) 表示当前的匹配长度)
于是我们需要一种可靠的 DS 来维护这个 \(\text{end-pos}\) 集,支持查找其中是否含有在某个值域中的元素。
我们注意到,\(\text{end-pos}\) 的关系可以构成一个 树形结构,因此可以向根的方向将集合合并。也就是说还需要高效的合并。
其实用 线段树合并 来做是维护整个 \(\text{end-pos}\) 是常用套路。为了合并时不破坏原有的信息,我们应在过程中新建结点(类似于可持久化)。
回归走转移的过程。
加上线段树,也许会这么写:
void trans(int& x, int& len, int l, int r, int c) {
for (; ; len = MS[MS[x].link].len, x = MS[x].link) {
if (MS[x].ch[c] && segt::find(MS[MS[x].ch[c]].eprt, l + len, r)) {
++len, x = MS[x].ch[c];
break;
}
if (x == 1) break;
}
}
但这样只有 96 pts。正确的写法应该是 逐步减小 这个 len,因为此时线段树的搜索区间不断增大,期间可能出现满足条件的情况,导致还没有跳后缀链接时就可以跳出。
附上满分的写法:
void trans(int& x, int& len, int l, int r, int c) {
while (true) {
if (MS[x].ch[c] && segt::find(MS[MS[x].ch[c]].eprt, l + len, r)) {
++len, x = MS[x].ch[c];
break;
}
if (!len) break;
if (MS[MS[x].link].len == --len) x = MS[x].link; // 逐步减小
}
}
时空复杂度:\(O(n\log n)\),这里 \(\Sigma = 26\) 视为常数。
Code
/*
* Author : _Wallace_
* Source : https://www.cnblogs.com/-Wallace-/
* Problem : NOI2018 你的名字
*/
#include <algorithm>
#include <cctype>
#include <cstring>
#include <cstdio>
#include <map>
using namespace std;
const int N = 5e5 + 5;
int n, q;
namespace io {
// fast io - by cyjian
const int __SIZE = (1 << 21) + 1;
char ibuf[__SIZE], *iS, *iT, obuf[__SIZE], *oS = obuf, *oT = oS + __SIZE - 1, __c, qu[55]; int __f, qr, _eof;
#define Gc() (iS == iT ? (iT = (iS = ibuf) + fread (ibuf, 1, __SIZE, stdin), (iS == iT ? EOF : *iS ++)) : *iS ++)
inline void flush () { fwrite (obuf, 1, oS - obuf, stdout), oS = obuf; }
inline void gc (char &x) { x = Gc(); }
inline void pc (char x) { *oS ++ = x; if (oS == oT) flush (); }
inline void pstr (const char *s) { int __len = strlen(s); for (__f = 0; __f < __len; ++__f) pc (s[__f]); }
inline void gstr (char *s) { for(__c = Gc(); __c < 32 || __c > 126 || __c == ' ';) __c = Gc();
for(; __c > 31 && __c < 127 && __c != ' ' && __c != '\n' && __c != '\r'; ++s, __c = Gc()) *s = __c; *s = 0; }
template <class I> inline bool gi (I &x) { _eof = 0;
for (__f = 1, __c = Gc(); (__c < '0' || __c > '9') && !_eof; __c = Gc()) { if (__c == '-') __f = -1; _eof |= __c == EOF; }
for (x = 0; __c <= '9' && __c >= '0' && !_eof; __c = Gc()) x = x * 10 + (__c & 15), _eof |= __c == EOF; x *= __f; return !_eof; }
template <class I> inline void print (I x) { if (!x) pc ('0'); if (x < 0) pc ('-'), x = -x;
while (x) qu[++ qr] = x % 10 + '0', x /= 10; while (qr) pc (qu[qr --]); }
struct Flusher_ {~Flusher_(){flush();}}io_flusher_;
};
namespace segt {
const int S = N << 6;
int lc[S], rc[S], total(0);
#define mid ((l + r) >> 1)
void insert(int& x, int p, int l = 1, int r = n) {
if (!x) x = ++total;
if (l == r) return;
if (p <= mid) insert(lc[x], p, l, mid);
else insert(rc[x], p, mid + 1, r);
}
int merge(int x, int y) {
if (!x || !y) return x | y;
int z = ++total;
lc[z] = merge(lc[x], lc[y]);
rc[z] = merge(rc[x], rc[y]);
return z;
}
bool find(int& x, int a, int b, int l = 1, int r = n) {
if (!x) return false;
if (a <= l && r <= b) return true;
if (a <= mid && find(lc[x], a, b, l, mid)) return true;
if (b > mid && find(rc[x], a, b, mid + 1, r)) return true;
return false;
}
}; // namespace segt
int b[N << 1], c[N];
template<int N, bool F> struct SAM {
struct Node {
int ch[26];
int link, len, eprt, dir;
} t[N << 1];
int total, last;
void extend(int c) {
int p = last, np = last = ++total;
t[np].len = t[p].len + 1, t[np].dir = np;
for (; p && !t[p].ch[c]; p = t[p].link)
t[p].ch[c] = np;
if (!p) {
t[np].link = 1;
} else {
int q = t[p].ch[c];
if (t[q].len == t[p].len + 1) {
t[np].link = q;
} else {
int nq = ++total;
t[nq] = t[q], t[nq].len = t[p].len + 1;
t[np].link = t[q].link = nq;
for (; p && t[p].ch[c] == q; p = t[p].link)
t[p].ch[c] = nq;
}
}
if (F) segt::insert(t[np].eprt, t[np].len);
}
void init(char* s) {
if (!F) fill(t, t + 1 + total, Node());
last = total = 1;
for (register int i = 0; s[i]; i++) extend(s[i] - 'a');
if (!F) return;
for (register int i = 1; i <= total; i++) ++c[t[i].len];
for (register int i = 1; i <= total; i++) c[i] += c[i - 1];
for (register int i = 1; i <= total; i++) b[c[t[i].len]--] = i;
for (register int i = total; i; i--)
t[t[b[i]].link].eprt = segt::merge(t[b[i]].eprt, t[t[b[i]].link].eprt);
}
Node& operator [] (int p) {
return t[p];
}
}; // struct SAM
SAM<N, true> MS;
SAM<N << 1, false> QS;
void trans(int& x, int& len, int l, int r, int c) {
while (true) {
if (MS[x].ch[c] && segt::find(MS[MS[x].ch[c]].eprt, l + len, r)) {
++len, x = MS[x].ch[c];
break;
}
if (!len) break;
if (MS[MS[x].link].len == --len) x = MS[x].link;
}
}
int lim[N];
long long solve(char* s, int l, int r) {
int x = 1;
for (register int i = 1; s[i - 1]; i++) {
int c = s[i - 1] - 'a';
lim[i] = lim[i - 1];
trans(x, lim[i], l, r, c);
}
QS.init(s);
long long ans = 0ll;
for (register int i = 2; i <= QS.total; i++) {
int tmp = QS[i].len - max(QS[QS[i].link].len, lim[QS[QS[i].dir].len]);
ans += (tmp > 0) ? tmp : 0;
}
return ans;
}
char s[N << 1];
signed main() {
freopen("name.in", "r", stdin);
freopen("name.out", "w", stdout);
io::gstr(s);
n = strlen(s);
MS.init(s);
io::gi(q);
while (q--) {
int l, r;
io::gstr(s), io::gi(l), io::gi(r);
io::print(solve(s, l, r));
io::pc('\n');
}
return 0;
}
【NOI2018】你的名字(SAM & 线段树合并)的更多相关文章
- [NOI2018]你的名字(SAM+线段树合并)
考虑l=1,r=n的68分,对S和T建SAM,对T的SAM上的每个节点,计算它能给答案带来多少贡献. T上节点x代表的本质不同的子串数为mx[x]-mx[fa[x]],然后需要去掉所代表子串与S的最长 ...
- NOI2018 你的名字——SAM+线段树合并
题目链接在这里洛谷/LOJ 题目大意 有一个串\(S\),每次询问给你一个串\(T\),两个数\(L\)和\(R\),问你\(T\)有多少个本质不同的子串不是\(S[L,R]\)的子串 SOLUTIO ...
- 【BZOJ5417】[NOI2018]你的名字(线段树,后缀自动机)
[BZOJ5417][NOI2018]你的名字(线段树,后缀自动机) 题面 BZOJ 洛谷 题解 首先考虑\(l=1,r=|S|\)的做法,对于每次询问的\(T\)串,暴力在\(S\)串的\(SAM\ ...
- UOJ#395. 【NOI2018】你的名字 字符串,SAM,线段树合并
原文链接https://www.cnblogs.com/zhouzhendong/p/UOJ395.html 题解 记得同步赛的时候这题我爆0了,最暴力的暴力都没调出来. 首先我们看看 68 分怎么做 ...
- P4770-[NOI2018]你的名字【SAM,线段树合并】
正题 题目链接:https://www.luogu.com.cn/problem/P4770 题目大意 给出一个长度为\(n\)的字符串\(S\).\(q\)次询问给出一个串\(T\)和一个区间\([ ...
- CF1037H Security——SAM+线段树合并
又是一道\(SAM\)维护\(endpos\)集合的题,我直接把CF700E的板子粘过来了QwQ 思路 如果我们有\([l,r]\)对应的\(SAM\),只需要在上面贪心就可以了.因为要求的是字典序比 ...
- luogu4770 [NOI2018]你的名字 (SAM+主席树)
对S建SAM,拿着T在上面跑 跑的时候不仅无法转移要跳parent,转移过去不在范围内也要跳parent(注意因为范围和长度有关,跳的时候应该把长度一点一点地缩) 这样就能得到对于T的每个前缀,它最长 ...
- 洛谷P4482 [BJWC2018]Border 的四种求法 字符串,SAM,线段树合并,线段树,树链剖分,DSU on Tree
原文链接https://www.cnblogs.com/zhouzhendong/p/LuoguP4482.html 题意 给定一个字符串 S,有 q 次询问,每次给定两个数 L,R ,求 S[L.. ...
- Codeforces 700E. Cool Slogans 字符串,SAM,线段树合并,动态规划
原文链接https://www.cnblogs.com/zhouzhendong/p/CF700E.html 题解 首先建个SAM. 一个结论:对于parent树上任意一个点x,以及它所代表的子树内任 ...
随机推荐
- kernel——Makefile, head.S ...
在Makefile中找到的重要信息: (1)连接脚本 通过连接脚本,知道的信息: (1)入口符号 stext (2)入口连接地址 0xC0000000 + 0x00008000 根据入口符号,可以找到 ...
- Python_入门第一篇【持续更新...】
1.准备 准备电脑 和 分区 1.准备配置稍高的电脑(后后期需要装虚拟机),分辨率1920*1080 2.分区: C→系统 D→Project E→软件安装盘 F→其他 准备编辑器 1.Sublime ...
- 学习笔记:[算法分析]数据结构与算法Python版[基本的数据结构-上]
线性结构Linear Structure ❖线性结构是一种有序数据项的集合,其中 每个数据项都有唯一的前驱和后继 除了第一个没有前驱,最后一个没有后继 新的数据项加入到数据集中时,只会加入到原有 某个 ...
- HTML5 localStorageXSS漏洞
localStorage基础 Window localStorage 属性 HTML5 提供了两种新的本地存储方案,sessionStorage和localStorage,统称WebStorage. ...
- ctf-misc-图片隐写术套路总结
1.直接右键notepad打开,搜索flag,如果图片很多的话,可以写py脚本也 可以打开后搜索全部打开文件 2.是一个压缩包,改了后缀 3.图片中藏了一个二维码,用Stegsolve加几次滤镜 ...
- FL Studio 插件使用教程 —— 3x Osc(上)
在FL Studio20 中,3x Osc是继TS404插件之后资历最老的插件之一,也是FL Studio20 中最重要.使用率最高的插件之一.相比别的FL Studio20内置插件,3x Osc 相 ...
- 怎么用Camtasia给视频添加片头片尾
有许多朋友现在喜欢自己拍摄一些小视频,现在不管是在抖音还是在B站,我们看到的大部分视频都有UP主自己制作的片头或片尾.片头做的好,甚至会有人因为片头而关注UP主,能吸引更多的人来观看视频. 所以,如果 ...
- 「CSP-S 2019」格雷码
[题目描述] 传送门 [题解] 题目中已经清楚地告诉你怎么用n位格雷码推n+1位格雷码, 直接二叉树模拟即可 注意要使用unsigned long long(如果这道题没有95分部分分,不知道有多少人 ...
- 日常踩坑-------新手使用idea
mybatis在idea的maven项目中的坑 今天遇到mybatis的报错,搞了好久才搞懂,在网上找了好久的相似案例,也没有搞定,先来看下网上常见的解决办法吧,相信也能解决大部分人的报错. 1.ma ...
- django绕过admin登录设置
在admin.py文件添加以下函数本文是转载:#绕过admin登录def allow_anonymous_user(): from django.contrib.auth.models import ...