其实很水的一道题吧....

题意是:每次给定一个串\(T\)以及\(l, r\),询问有多少个字符串\(s\)满足,\(s\)是\(T\)的子串,但不是\(S[l .. r]\)的子串


统计\(T\)本质不同的串,建个后缀自动机

然后自然的可以想到,对于每个\(T\)的子串,它对应了一个\(right\)集合

那么,它应该会被这个\(right\)集合所限制

考虑对于每个\(i\),求出最小的\(l\)使得\(T[l .. i]\)存在于\(S[l..r]\)中

这个可以套个线段树转移

然后就没了.....


如果不需要统计\(T\)本质不同的串,又怎么做呢?

统计的时候乘上\(right\)集合大小就行


#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std; #define ri register int
#define ll long long
#define rep(io, st, ed) for(ri io = st; io <= ed; io ++)
#define drep(io, ed, st) for(ri io = ed; io >= st; io --) #define gc getchar
inline int read() {
int p = 0, w = 1; char c = gc();
while(c > '9' || c < '0') { if(c == '-') w = -1; c = gc(); }
while(c >= '0' && c <= '9') p = p * 10 + c - '0', c = gc();
return p * w;
} const int sid = 1005000;
const int eid = 30000000 + 5; struct SAM { int id, fa[sid], mx[sid];
int go[sid][26], mc[sid]; inline int newnode() {
++ id;
fa[id] = mx[id] = mc[id] = 0;
memset(go[id], 0, sizeof(go[id]));
return id;
} inline void init() {
id = 0;
newnode();
} inline int extend(int lst, int c, int pos) {
int np = newnode(), p = lst;
mx[np] = mx[p] + 1; mc[np] = pos;
for( ; p && !go[p][c]; p = fa[p])
go[p][c] = np;
if(!p) fa[np] = 1;
else {
int q = go[p][c];
if(mx[p] + 1 == mx[q]) fa[np] = q;
else {
int nq = newnode(); mx[nq] = mx[p] + 1;
fa[nq] = fa[q]; fa[np] = fa[q] = nq;
memcpy(go[nq], go[q], sizeof(go[q]));
for( ; p && go[p][c] == q; p = fa[p])
go[p][c] = nq;
}
}
return np;
} } S, T; int q, n, m, seg;
char s[sid], t[sid];
int nc[sid], ip[sid], w[sid], val[sid];
int rt[sid], ls[eid], rs[eid]; inline int merge(int x, int y) {
if(!x || !y) return x + y;
int o = ++ seg;
ls[o] = merge(ls[x], ls[y]);
rs[o] = merge(rs[x], rs[y]);
return o;
} inline void ins(int &o, int l, int r, int p) {
o = ++ seg;
if(l == r) return;
int mid = (l + r) >> 1;
if(p <= mid) ins(ls[o], l, mid, p);
else ins(rs[o], mid + 1, r, p);
} inline bool qry(int o, int l, int r, int ml, int mr) {
if(ml > r || mr < l || ml > mr || !o) return 0;
if(ml <= l && mr >= r) return 1;
int mid = (l + r) >> 1;
if(qry(ls[o], l, mid, ml, mr)) return 1;
else return qry(rs[o], mid + 1, r, ml, mr);
} inline void init() {
S.init();
int lst = 1;
rep(i, 1, n) lst = S.extend(lst, s[i] - 'a', i);
int id = S.id;
rep(i, 1, id) nc[S.mx[i]] ++;
rep(i, 1, n) nc[i] += nc[i - 1];
rep(i, 1, id) ip[nc[S.mx[i]] --] = i;
rep(i, 1, id)
if(S.mc[i])
ins(rt[i], 1, n, S.mc[i]);
drep(i, id, 1) {
int o = ip[i], f = S.fa[o];
rt[f] = merge(rt[f], rt[o]);
}
} void Match(int l, int r) {
int o = 1, nl = 0;
rep(i, 1, m) {
int c = t[i] - 'a';
while(1)
{
int nxt = S.go[o][c], f = S.fa[o];
if(nxt && qry(rt[nxt], 1, n, l + nl, r))
{
nl ++; o = nxt;
break;
}
if(!nl) break; nl --;
if(nl == S.mx[f]) o = f;
}
w[i] = nl;
}
} int main() {
scanf("%s", s + 1);
n = strlen(s + 1);
init(); q = read();
rep(i, 1, q) { scanf("%s", t + 1);
m = strlen(t + 1); T.init();
int lst = 1;
rep(j, 1, m) lst = T.extend(lst, t[j] - 'a', j); int l = read(), r = read();
Match(l, r); int id = T.id;
rep(i, 1, id) nc[i] = val[i] = 0;
rep(i, 1, id) nc[T.mx[i]] ++;
rep(i, 1, id) nc[i] += nc[i - 1];
rep(i, 1, id) ip[nc[T.mx[i]] --] = i;
drep(i, id, 1) {
int o = ip[i], f = T.fa[o];
if(T.mc[o]) val[o] = w[T.mc[o]];
val[f] = max(val[f], val[o]);
} ll ans = 0;
rep(i, 1, id) ans += max(T.mx[i] - max(T.mx[T.fa[i]], val[i]), 0);
printf("%lld\n", ans);
}
return 0;
}

luogu4770 [NOI2018]你的名字 后缀自动机 + 线段树合并的更多相关文章

  1. bzoj5417/luoguP4770 [NOI2018]你的名字(后缀自动机+线段树合并)

    bzoj5417/luoguP4770 [NOI2018]你的名字(后缀自动机+线段树合并) bzoj Luogu 给出一个字符串 $ S $ 及 $ q $ 次询问,每次询问一个字符串 $ T $ ...

  2. BZOJ5417[Noi2018]你的名字——后缀自动机+线段树合并

    题目链接: [Noi2018]你的名字 题目大意:给出一个字符串$S$及$q$次询问,每次询问一个字符串$T$有多少本质不同的子串不是$S[l,r]$的子串($S[l,r]$表示$S$串的第$l$个字 ...

  3. BZOJ.5417.[NOI2018]你的名字(后缀自动机 线段树合并)

    LOJ 洛谷 BZOJ 考虑\(l=1,r=|S|\)的情况: 对\(S\)串建SAM,\(T\)在上面匹配,可以得到每个位置\(i\)的后缀的最长匹配长度\(mx[i]\). 因为要去重,对\(T\ ...

  4. NOI 2018 你的名字 (后缀自动机+线段树合并)

    题目大意:略 令$ION2017=S,ION2018=T$ 对$S$建$SAM$,每次都把$T$放进去跑,求出结尾是i的前缀串,能匹配上$S$的最长后缀长度为$f_{i}$ 由于$T$必须在$[l,r ...

  5. [NOI2018]你的名字(后缀自动机+线段树)

    题目描述 小A 被选为了ION2018 的出题人,他精心准备了一道质量十分高的题目,且已经把除了题目命名以外的工作都做好了. 由于ION 已经举办了很多届,所以在题目命名上也是有规定的,ION 命题手 ...

  6. BZOJ3413: 匹配(后缀自动机 线段树合并)

    题意 题目链接 Sol 神仙题Orz 后缀自动机 + 线段树合并... 首先可以转化一下模型(想不到qwq):问题可以转化为统计\(B\)中每个前缀在\(A\)中出现的次数.(画一画就出来了) 然后直 ...

  7. cf666E. Forensic Examination(广义后缀自动机 线段树合并)

    题意 题目链接 Sol 神仙题Orz 后缀自动机 + 线段树合并 首先对所有的\(t_i\)建个广义后缀自动机,这样可以得到所有子串信息. 考虑把询问离线,然后把\(S\)拿到自动机上跑,同时维护一下 ...

  8. [Luogu5161]WD与数列(后缀数组/后缀自动机+线段树合并)

    https://blog.csdn.net/WAautomaton/article/details/85057257 解法一:后缀数组 显然将原数组差分后答案就是所有不相交不相邻重复子串个数+n*(n ...

  9. 模板—字符串—后缀自动机(后缀自动机+线段树合并求right集合)

    模板—字符串—后缀自动机(后缀自动机+线段树合并求right集合) Code: #include <bits/stdc++.h> using namespace std; #define ...

随机推荐

  1. 【算法】Base64编码

    1.说明 Base64是网络上最常见的用于传输8Bit字节码的编码方式之一,Base64就是一种基于64个可打印字符来表示二进制数据的方法. 2.编码 ASCII码 -> 十六进制码 -> ...

  2. flask基础之请求处理核心机制(五)

    前言 总结一下flask框架的请求处理流程. 系列文章 flask基础之安装和使用入门(一) flask基础之jijia2模板使用基础(二) flask基础之jijia2模板语言进阶(三) flask ...

  3. 利用JS验证查询参数-选择月份后必须选择年份

    js代码: function queryAgentInfo(){ // 标记 var flag=false; //遍历所有月份 $(".month").each(function( ...

  4. maven bat脚本打包

    脚本内容: @echo off echo 正在设置临时环境变量 set JAVA_HOME=C:\Program Files\Java\jdk1.6.0_45 set MAVEN_HOME=D:\ap ...

  5. 【Android开发】之MediaPlayer的错误分析

    最近在做媒体播放器,使用了Android自带的MediaPlayer,经常性会碰到MediaPlayer报错的情况,找过网上的,感觉总结的不是很好或者比较散.下面,我来总结一下使用MediaPlaye ...

  6. [how to]HBase Snapshots原理与使用

    1.简介 Snapshots即快照的意思,作用于表上.在对于表做快照的时候不会造成文件的拷贝,如不会对HFile文件进行拷贝而是以链接的方式链接到元表的HFile上.可以说它是一种元数据的集合,可以快 ...

  7. CentOS7网卡改名

    # vi /etc/sysconfig/grub 修改上面内容中的GRUB_CMDLINE_LINUX的值,增加net.ifnames=0 biosdevname=0 执行:grub2-mkconfi ...

  8. Vue.js 基础快速入门

    Vue.js是一个JavaScript MVVM库,它是以数据驱动和组件化的思想构建的.Vue.js提供了简洁.易于理解的API,使得我们能够快速地上手并使用Vue.js 如果之前已经习惯了用jQue ...

  9. 洛谷P2002消息扩散

    传送门啦 这个题就是tarjan强连通分量与入度的例题了. 思路: 利用缩点的思想,先预处理一下所有的强连通分量,然后把每个强连通分量内的所有节点看做一个节点,然后处理一张新图,然后检查每个点的入度, ...

  10. 开发者常用的 Sublime Text 3 插件

    1.官网下载 Sublime Text 3 (已有安装包的,请忽略) Sublime Text 官网下载地址 : http://www.sublimetext.com/ 2.打开 Sublime Te ...