【洛谷4770/UOJ395】[NOI2018]你的名字(后缀数组_线段树合并)
题目:
分析:
一个很好的SAM应用题……
一句话题意:给定一个字符串\(S\)。每次询问给定字符串\(T\)和两个整数\(l\)、\(r\),求\(T\)有多少个本质不同的非空子串不是\(S[l,r]\)的子串。
首先显然是“正难则反”,求有多少个本质不同的非空子串是\(S[l,r]\)的子串(下面的“答案”一词指的是这个值)。先考虑没有\(l\)和\(r\)限制的情况。分别处理询问。对于\(S\)建出后缀自动机。枚举\(T\)的所有前缀,在\(S\)的后缀自动机上走(匹配)。每个前缀对答案的贡献就是这个前缀有多少后缀出现在\(S\)中,即当前在后缀自动机上匹配的深度。
但是直接把所有贡献都加起来是错的,因为没有保证本质不同。于是对\(T\)也建出后缀自动机,不统计每个前缀的贡献,而是统计每个结点的贡献,即统计每个结点对应的本质不同的字符串中有多少个在\(S\)中出现。记\(lim[i]\)为\(T\)的前缀\(i\)有多少个后缀在\(S\)中出现,那么一个结点\(p\)的贡献就是\(max(0,min(lim[i],max[p])-max[fa[p]])\),其中\(i\)是\(p\)的\(Right\)集合中的任意一个点。由于以\(Right\)集合中任意一点结尾的,长度不超过\(max[p]\)的子串都是相同的,所以\(i\)的选取不会影响\(min(lim[i],max[p])\)的值。
至于带\(l\)和\(r\)限制的情况,用线段树合并求出\(S\)的后缀自动机上每个结点的\(Right\)集合。\(T\)在\(S\)的后缀自动机上匹配时,要查询要走到的结点的\(Right\)集合与\([l+len,r]\)的交集是否非空(\(len\)是当前匹配长度,即查询是否有一个长为\(len+1\)的子串在\(S[l,r]\)中出现过)。
代码:
统计答案的时候直接算了这个结点对应的字符串中有多少个不是\(S[l,r]\)的子串,因此式子和上面分析的略有不同。
#include <cstdio>
#include <algorithm>
#include <cctype>
#include <cstring>
#include <string>
#define _ 0
using namespace std;
namespace zyt
{
const int N = 5e5 + 10, B = 20;
template<typename T>
inline bool read(T &x)
{
char c;
bool f = false;
x = 0;
do
c = getchar();
while (c != EOF && c != '-' && !isdigit(c));
if (c == EOF)
return false;
if (c == '-')
f = true, c = getchar();
do
x = x * 10 + c - '0', c = getchar();
while (isdigit(c));
if (f)
x = -x;
return true;
}
inline void read(string &s)
{
char buf[N];
scanf("%s", buf);
s = buf;
}
template<typename T>
inline void write(T x)
{
static char buf[20];
char *pos = buf;
if (x < 0)
putchar('-'), x = -x;
do
*pos++ = x % 10 + '0';
while (x /= 10);
while (pos > buf)
putchar(*--pos);
}
typedef long long ll;
const int CH = 26;
inline int ctoi(const char c)
{
return c - 'a';
}
namespace Segment_Tree
{
struct node
{
int sum, s[2];
}tree[(N << 1) * B];
int cnt, head[N << 1];
int insert(const int lt, const int rt, const int pos)
{
int rot = ++cnt;
++tree[rot].sum;
if (lt == rt)
return rot;
int mid = (lt + rt) >> 1;
if (pos <= mid)
tree[rot].s[0] = insert(lt, mid, pos);
else
tree[rot].s[1] = insert(mid + 1, rt, pos);
return rot;
}
int merge(const int rot1, const int rot2)
{
if (!rot1)
return rot2;
if (!rot2)
return rot1;
int rot = ++cnt;
int lt = merge(tree[rot1].s[0], tree[rot2].s[0]);
int rt = merge(tree[rot1].s[1], tree[rot2].s[1]);
tree[rot] = (node){tree[rot1].sum + tree[rot2].sum, lt, rt};
return rot;
}
int query(const int rot, const int lt, const int rt, const int ls, const int rs)
{
if (ls > rs)
return 0;
if (ls <= lt && rt <= rs)
return tree[rot].sum;
int mid = (lt + rt) >> 1, ans = 0;
if (ls <= mid)
ans += query(tree[rot].s[0], lt, mid, ls, rs);
if (rs > mid)
ans += query(tree[rot].s[1], mid + 1, rt, ls, rs);
return ans;
}
}
struct SAM
{
int last, cnt;
struct node
{
int max, fa, first, s[CH];
}tree[N << 1];
void init()
{
last = cnt = 1;
memset(tree[1].s, 0, sizeof(int[CH]));
}
void insert(const char c)
{
int x = ctoi(c);
int np = ++cnt, p = last;
memset(tree[np].s, 0, sizeof(int[CH]));
tree[np].max = tree[p].max + 1;
while (p && !tree[p].s[x])
tree[p].s[x] = np, p = tree[p].fa;
if (!p)
tree[np].fa = 1;
else
{
int q = tree[p].s[x];
if (tree[p].max + 1 == tree[q].max)
tree[np].fa = q;
else
{
int nq = ++cnt;
memcpy(tree[nq].s, tree[q].s, sizeof(int[CH]));
tree[nq].first = tree[q].first;
tree[nq].max = tree[p].max + 1;
tree[nq].fa = tree[q].fa;
tree[np].fa = tree[q].fa = nq;
while (p && tree[p].s[x] == q)
tree[p].s[x] = nq, p = tree[p].fa;
}
}
last = np;
}
static int buf[N << 1];
void topo()
{
static int count[N];
int maxx = 0;
memset(count, 0, sizeof(count));
for (int i = 1; i <= cnt; i++)
++count[tree[i].max], maxx = max(maxx, tree[i].max);
for (int i = 1; i <= maxx; i++)
count[i] += count[i - 1];
for (int i = cnt; i > 0; i--)
buf[count[tree[i].max]--] = i;
}
void build(const string &s, const bool segment)
{
using Segment_Tree::head;
init();
for (int i = 0; i < s.size(); i++)
{
insert(s[i]);
tree[last].first = i;
if (segment)
head[last] = Segment_Tree::insert(0, s.size() - 1, i);
}
if (segment)
{
topo();
for (int i = cnt; i > 0; i--)
{
using Segment_Tree::head;
head[tree[buf[i]].fa] = Segment_Tree::merge(head[tree[buf[i]].fa], head[buf[i]]);
}
}
}
}s1, s2;
int SAM::buf[N << 1], lim[N];
string s;
ll solve(const string &t, const int l, const int r)
{
using Segment_Tree::head;
using Segment_Tree::query;
SAM::node *tree = s1.tree;
int now = 1, len = 0;
for (int i = 0; i < t.size(); i++)
{
int x = ctoi(t[i]), nxt = tree[now].s[x];
while (now && !nxt)
now = tree[now].fa, nxt = tree[now].s[x], len = tree[now].max;
while (now)
{
int tmp = 0;
while (len >= 0 && !(tmp = query(head[nxt], 0, s.size() - 1, l + len, r)))
{
--len;
if (len == tree[tree[now].fa].max)
now = tree[now].fa, nxt = tree[now].s[x];
}
if (tmp)
{
now = nxt, ++len;
break;
}
else
now = tree[now].fa, nxt = tree[now].s[x], len = tree[now].max;
}
if (!now)
now = 1, len = 0;
lim[i] = len;
}
ll ans = 0;
for (int i = 1; i <= s2.cnt; i++)
ans += max(0, s2.tree[i].max - max(s2.tree[s2.tree[i].fa].max, lim[s2.tree[i].first]));
return ans;
}
int work()
{
int T;
read(s), read(T);
s1.build(s, true);
while (T--)
{
using namespace Segment_Tree;
static string t;
int l, r;
read(t), read(l), read(r);
--l, --r;
s2.build(t, false);
write(solve(t, l, r)), putchar('\n');
}
return (0^_^0);
}
}
int main()
{
return zyt::work();
}
【洛谷4770/UOJ395】[NOI2018]你的名字(后缀数组_线段树合并)的更多相关文章
- 【洛谷4770】 [NOI2018]你的名字(SAM,线段树合并)
传送门 洛谷 Solution 做过的比较玄学的后缀自动机. 果然就像\(Tham\)所讲,后缀自动机这种东西考场考了不可能做的出来的... 考虑如果\(l=1,r=|S|\)的怎么做? 直接建后缀自 ...
- NOI2018 你的名字 后缀自动机_线段树合并_可持久化
相当复杂的一道题,同样也相当优美.考察的知识点很多:权值线段树的可持久化合并,后缀自动机,后缀树... 考虑 $68pts$ $l=1,r=|s|$的数据:这部分相对好做一些,不过思维难度对我来说已 ...
- UOJ #395 BZOJ 5417 Luogu P4770 [NOI2018]你的名字 (后缀自动机、线段树合并)
NOI2019考前做NOI2018题.. 题目链接: (bzoj) https://www.lydsy.com/JudgeOnline/problem.php?id=5417 (luogu) http ...
- 洛谷P4770 [NOI2018]你的名字 [后缀自动机,线段树合并]
传送门 思路 按照套路,直接上后缀自动机. 部分分:\(l=1,r=|S|\) 首先把\(S\)和\(T\)的后缀自动机都建出来. 考虑枚举\(T\)中的右端点\(r\),查询以\(r\)结尾的串最长 ...
- 「BZOJ2733」「洛谷3224」「HNOI2012」永无乡【线段树合并】
题目链接 [洛谷] 题解 很明显是要用线段树合并的. 对于当前的每一个连通块都建立一个权值线段树. 权值线段树处理操作中的\(k\)大的问题. 如果需要合并,那么就线段树暴力合并,时间复杂度是\(nl ...
- 【洛谷4482】Border的四种求法(后缀自动机_线段树合并_链分治)
这题我写了一天后交了一发就过了我好兴奋啊啊啊啊啊啊 题目 洛谷 4482 分析 这题明明可以在线做的,为什么我见到的所有题解都是离线啊 -- 什么时候有机会出一个在线版本坑人. 题目的要求可以转化为求 ...
- 洛谷P4482 [BJWC2018]Border 的四种求法 字符串,SAM,线段树合并,线段树,树链剖分,DSU on Tree
原文链接https://www.cnblogs.com/zhouzhendong/p/LuoguP4482.html 题意 给定一个字符串 S,有 q 次询问,每次给定两个数 L,R ,求 S[L.. ...
- BZOJ5291/洛谷P4458/LOJ#2512 [Bjoi2018]链上二次求和 线段树
原文链接http://www.cnblogs.com/zhouzhendong/p/9031130.html 题目传送门 - LOJ#2512 题目传送门 - 洛谷P4458 题目传送门 - BZOJ ...
- 洛谷P3994 Highway(树形DP+斜率优化+可持久化线段树/二分)
有点类似NOI2014购票 首先有方程$f(i)=min\{f(j)+(dep_i-dep_j)*p_i+q_i\}$ 这个显然是可以斜率优化的... $\frac {f(j)-f(k)}{dep_j ...
随机推荐
- python socket实现文件传输(防粘包)
1.文件传输的要点: 采用iterator(迭代器对象)迭代读取,提高读取以及存取效率: 通过for line in file_handles逐行conn.send(): 2.socket粘包问题: ...
- 热词解析(9) — hangry
今天给大家介绍一个非常有趣.又超级实用的词!!中文叫"饿极而怒",英文叫... 不知道你有没有这样的经历,当你饿着肚子等着你妈做饭,结果你妈却在麻将桌上不下来,你就越来越饿,越饿越 ...
- 多校 1010 Taotao Picks Apples(补题)
>>点击进入原题<< 思路:题解很有意思,适合线段树进阶 考虑每次修改不叠加,因此我们可以从如何对原序列进行预处理着手.通过观察可以发现,将原序列从任意位置断开,我们可以通过分 ...
- Python基础(九) 内置模块
今天学习Python中的模块,模块类似于函数式编程和面向过程编程,函数式编程则完成一个功能,其他代码用来调用即可,提供了代码的重用性和代码间的耦合.而对于一个复杂的功能来,可能需要多个函数才能完成(函 ...
- RNN and Language modeling in TensorFlow
RNNs and Language modeling in TensorFlow From feed-forward to Recurrent Neural Networks (RNNs) In th ...
- [K/3Cloud]K3Cloud的移动审批方面
基于最新的K3Cloud2的SP2,当前K3Cloud上所有的工作流都可以在移动手机上进行移动审批,具体如下: K/3 Cloud 支持移动审批,支持安卓和IOS. 关于申请试用: 1.打开浏览器,进 ...
- Swift具体解释之六----------------枚举、结构体、类
枚举.结构体.类 注:本文为作者自己总结.过于基础的就不再赘述 ,都是亲自測试的结果.如有错误或者遗漏的地方.欢迎指正,一起学习. 1.枚举 枚举是用来定义一组通用类型的一组相关值 ,关键字enum ...
- 在psql客户端中修改函数
\ef 创建一个新的函数. \df 显示已经创建的函数. \df+ somefunc 显示这个函数的详细定义 \ef somefunc 编辑这个函数, 编辑保存退出之后,要执行 \g ,刚才 ...
- C++学习之可变参数的函数与模板
所谓可变参数指的是函数的参数个数可变,参数类型不定的函数.为了编写能处理不同数量实参的函数,C++11提供了两种主要的方法:如果所有的实参类型相同,可以传递一个名为initializer_list的标 ...
- Fedora下克隆Octopress博客
我在自己的github上搭建了一个Octopress博客(http://songlee24.github.io/),用于不定期的写一些学习笔记和心得体会.但是有时候由于换了电脑或者重装了linux系统 ...