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

题意是:每次给定一个串\(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. IIS 问题集锦

    本文主要记录IIS中遇到的各种问题以及注意事项 一.在IIS中.NET Framework的版本选择为什么没有v3.0,v3.5? 首先需要澄清的是这里有两个关于版本的东西:ASP.NET和.NET ...

  2. linux环境下mysql默认是区分表名大小写的

    在linux环境下,mysql默认表明是区分大小写的,我们可以查看全局变量发现: mysql> show variables like 'lower%'; +------------------ ...

  3. 【FCS NOI2018】福建省冬摸鱼笔记 day6【FJOI 2018】福建省选混分滚蛋记 day1

    记录一下day6发生的事情吧. 7:30 到达附中求索碑,被人膜,掉RP. 7:50 进考场,6楼的最后一排的最左边的位置,世界上最角落的地方,没有任何想法. 发现电脑时间和别人不一样,赶快调了一下. ...

  4. 戴尔游匣5577安装 ubuntu/mint

    这里以mint为例. 做好usb启动盘后, 启动到读秒的时候按上下方向键唤出如下选项: 在第二个选项里按 tab 键(上图界面有提示) 进去后有如下界面: 在后面加上参数: nouveau.modes ...

  5. zookeeper zkClient api 使用

    操作步骤: 一.引入zkclient的jar包(maven方式) <dependency> <groupId>com.101tec</groupId> <ar ...

  6. 比特币pow算法介绍

    Proof Of Work 工作量证明 借鉴了 哈希现金(Hashcash)-1997年 英国密码学专家亚当.贝克(Adam Back) 用工作量证明系统解决了互联网垃圾邮件问题,它要求计算机在获得发 ...

  7. iOS开发之删除Provisioning Profiles方法

    1.在finder下打开go -> go to folder输入: ~/Library/MobileDevice/Provisioning Profiles 2.查看上面的列表,按照时间顺序删除 ...

  8. spring 学习之二 AOP编程

    AOP概念 AOP, aspect oriented programing,翻译过来就是面向切面编程的意思,那什么叫面向切面编程呢?相对于之前传统的纵向继承方式来对原有功能进行功能扩展, 面向切面编程 ...

  9. SpringMVC 返回JSON数据的配置

    spring-mvc-config.xml(文件名称请视具体情况而定)配置文件: <!-- 启动Springmvc注解驱动 --> <mvc:annotation-driven> ...

  10. @PrePersist

    @PrePersistpublic void prePersist() { updatedAt = new Timestamp(System.currentTimeMillis()); created ...