既然字符串的总长一定,不妨对于每个询问中的 \(s_k\) 的长度根号分治,假定分治阈值为 \(B\)。下面令 \(L\) 为所有串长度总和。

对于长度大于 \(B\) 的字符串,这样的不同字符串至多有 \(\frac{L}{B}\) 个,考虑对于每个字符串建立 AC 自动机,然后暴力匹配出其他字符串的出现次数,并 \(O(n)\) 建立一个线段树来回答询问,这一部分复杂度是 \(O(\frac{L^2}{B} + q \log n)\) 的。

对于长度小于等于 \(B\) 的字符串,它的前缀至多有 \(B\) 个,映射到 AC 自动机上 \(B\) 个点,我们要查询这些点在失配树上到祖先的路径上处于区间 \([l,r]\) 内的终止节点出现最多的终止节点出现次数。

不妨假定这个终止节点是点 \(x\),根据 AC 自动机的性质,其产生贡献的前缀节点在失配树上 \(x\) 的子树内,这提示我们,无论如何产生贡献的前缀节点都在某一个子树内。并且这个子树内的前缀节点也都产生了贡献。

所以考虑建出所有前缀节点的虚树,那么我们枚举虚树上的节点,问题转变为判断这个节点是否会产生贡献。

显然这是一个二维数点问题,不妨对 \(r\) 做一遍扫描线,问题转变为点修链查最大值,再将其转变为子树修改单点查最大值,考虑用一个 \(O(\sqrt L)\) 修改 \(O(1)\) 查询的分块维护,由于虚树大小是 \(O(B)\) 的,所以这部分复杂度是 \(O(L \sqrt L + L \times B + L \log L)\)。

显然当 \(B = \sqrt L\) 的时候取到最小值 \(O(L \sqrt L + q \log n + L \log L)\) 此时空间也是 \(O(L)\) 的。

注意时间空间常数问题。

要是你被卡常了可以尝试一个经典的 trick,将遍历树的过程用在树的 dfs 序上扫描完成。

#include <bits/stdc++.h>
#define query(x) (max(a[x],tag[bp[x]]))
using namespace std;
const int maxn = 1e5 + 5;
const int warma = 707;
string s[maxn];
int Len[maxn];
struct Query {
Query(int L, int R, int ID) {
l = L, r = R, id = ID;
}
int l, r, id;
};
int n, q;
vector<Query> Q[maxn];
int answer[maxn];
vector<int> Vtree;//虚树
int L[maxn * 5], R[maxn * 5], Node[maxn * 5];
int bp[maxn * 5];
int son[maxn * 5][26], fail[maxn * 5], rt, tot, dfncnt;
int sz[maxn * 5], Hson[maxn * 5], top[maxn * 5], dep[maxn * 5];
vector< pair<int, int>> w[maxn];
vector<int> edge[maxn * 5];
vector<int> fa[maxn];
vector<int> road[maxn * 5]; //虚树上的节点
int endpos[maxn * 5];
int found[maxn];
inline void insert(int pos) {
int len = s[pos].size(), now = rt; for (register int i = 0; i < len; i = -~i) {
if (son[now][s[pos][i] - 'a'] == 0)
son[now][s[pos][i] - 'a'] = ++tot; now = son[now][s[pos][i] - 'a'];
fa[pos].push_back(now);
} if (endpos[now] == 0)
found[pos] = endpos[now] = pos;
else
found[pos] = endpos[now];
}
inline void build() {
queue<int> q; for (register int i = 0; i < 26; i = -~i)
if (son[rt][i])
fail[son[rt][i]] = rt, q.push(son[rt][i]); while (q.size() > 0) {
int u = q.front();
q.pop(); for (register int i = 0; i < 26; i = -~i) {
if (son[u][i]) {
fail[son[u][i]] = son[fail[u]][i];
q.push(son[u][i]);
} else
son[u][i] = son[fail[u]][i];
}
} for (register int i = 1; i <= tot; i = -~i) {
edge[fail[i]].push_back(i);
}
}
inline void dfs(int u) {
L[u] = ++dfncnt, Node[dfncnt] = u, sz[u] = 1; for (register int i = 0; i < edge[u].size(); i = -~i) {
dep[edge[u][i]] = dep[u] + 1;
dfs(edge[u][i]);
sz[u] += sz[edge[u][i]]; if (Hson[u] == -1 || sz[edge[u][i]] > sz[Hson[u]])
Hson[u] = edge[u][i];
} R[u] = dfncnt;
}
inline void HLD(int u, int tp) {
top[u] = tp; for (register int i = 0; i < edge[u].size(); i = -~i) {
if (edge[u][i] != Hson[u])
HLD(edge[u][i], edge[u][i]);
} if (Hson[u] != -1)
HLD(Hson[u], tp);
}
inline int LCA(int u, int v) {
while (top[u] != top[v]) {
if (dep[top[u]] < dep[top[v]])
swap(u, v); u = fail[top[u]];
} if (dep[u] < dep[v])
swap(u, v); return v;
}
int tag[maxn], a[maxn * 5];
inline void cover(int l, int r, int v) {
int bl = bp[l], br = bp[r]; if (bl == br) {
for (register int i = l; i <= r; i = -~i)
a[i] = v; return ;
} for (register int i = bl + 1; i < br; i = -~i)
tag[i] = v; for (register int i = l; i <= bl * warma; i = -~i)
a[i] = v; for (register int i = (br - 1) * warma + 1; i <= r; i = -~i)
a[i] = v;
}
struct ASK {
int l, k, id;
ASK(int L, int K, int ID) {
l = L, k = K, id = ID;
}
};
int tr[maxn << 2];
inline void build(int cur, int lt, int rt) {
if (lt == rt) {
tr[cur] = sz[fa[lt].back()];
return ;
} int mid = (lt + rt) >> 1;
build(cur << 1, lt, mid);
build(cur << 1 | 1, mid + 1, rt);
tr[cur] = max(tr[cur << 1], tr[cur << 1 | 1]);
}
inline int query_mx(int cur, int l, int r, int lt, int rt) {
if (l <= lt && rt <= r)
return tr[cur]; if (r < lt || l > rt)
return 0; int mid = (lt + rt) >> 1;
return max(query_mx(cur << 1, l, r, lt, mid), query_mx(cur << 1 | 1, l, r, mid + 1, rt));
}
inline bool cmp(int A, int B) {
return L[A] < L[B];
}
vector<ASK> ask[maxn];
inline void V_build(int u) {
for (register int i = 0; i < road[u].size(); i = -~i) {
int v = road[u][i];
V_build(v);
sz[u] += sz[v];
}
}
inline bool cmp1(pair<int, int> A, pair<int, int> B) {
return A.second > B.second;
}
inline string sread() {
string ans;
char ch = getchar(); while (ch < 'a' || ch > 'z')
ch = getchar(); while (ch >= 'a' && ch <= 'z')
ans += ch, ch = getchar(); return ans;
}
inline int read() {
bool flag = false;
int x = 0;
char ch = getchar(); while (ch < '0' || ch > '9') {
if (ch == '-')
flag = true; ch = getchar();
} while (ch >= '0' && ch <= '9') {
x = (x << 1) + (x << 3) + ch - '0';
ch = getchar();
} return flag ? -x : x;
}
void write(int x) {
if (x < 0) {
putchar('-');
x = -x;
} if (x > 9)
write(x / 10); putchar(x % 10 ^ 48);
}
int flag[maxn];
int B;
set<int> S;
int root;
int main() {
memset(Hson, -1, sizeof(Hson));
n = read();
q = read(); for (register int i = 1; i <= n; i = -~i)
s[i] = sread(), Len[i] = s[i].size(), B += Len[i]; B = sqrt(B) * 1.4; for (register int i = 1; i <= n; i = -~i)
insert(i); for (register int i = 1; i <= q; i = -~i) {
int l, r, k;
l = read();
r = read();
k = read(); if (Len[k] <= B) {
flag[k] = 1;
ask[r].push_back(ASK(l, k, i));
} else {
Q[found[k]].push_back(Query(l, r, i));
}
} build();
dfs(rt);
HLD(rt, rt); for (register int i = 1; i <= dfncnt; i++)
bp[i] = (i - 1) / warma + 1; for (register int i = 0; i <= tot; i++)
sz[i] = 0; for (register int i = 1; i <= n; i = -~i) {
if (flag[i] == 0)
continue; S.clear();
Vtree.clear();
root = -1; for (register int j = 0; j < fa[i].size(); j = -~j)
Vtree.push_back(fa[i][j]), S.insert(fa[i][j]), sz[fa[i][j]]++; sort(Vtree.begin(), Vtree.end(), cmp); for (register int j = 0; j < Vtree.size() - 1; j = -~j) {
int u = LCA(Vtree[j], Vtree[j + 1]);
S.insert(u);
} Vtree.clear(); for (set<int>::iterator i = S.begin(); i != S.end(); ++i) {
Vtree.push_back((*i)); if (root == -1 || dep[(*i)] < dep[root])
root = (*i);
} sort(Vtree.begin(), Vtree.end(), cmp); for (register int j = 0; j < Vtree.size() - 1; j = -~j) {
int u = LCA(Vtree[j], Vtree[j + 1]);
road[u].push_back(Vtree[j + 1]);
} V_build(root); for (register int j = 0; j < Vtree.size(); j = -~j)
w[i].push_back(make_pair(Vtree[j], sz[Vtree[j]])); sort(w[i].begin(), w[i].end(), cmp1); for (register int j = 0; j < Vtree.size(); j = -~j) {
sz[Vtree[j]] = 0;
road[Vtree[j]].clear();
}
} for (register int i = 1; i <= n; i = -~i) {
cover(L[fa[i].back()], R[fa[i].back()], i); for (register int j = 0; j < ask[i].size(); j = -~j) {
for (register int k = 0; k < w[ask[i][j].k].size(); k = -~k) {
if (query(L[w[ask[i][j].k][k].first]) >= ask[i][j].l) {
answer[ask[i][j].id] = max(answer[ask[i][j].id], w[ask[i][j].k][k].second);
break;
}
}
}
} for (register int i = 1; i <= n; i = -~i) {
if (Q[i].size() > 0) {
for (register int j = 0; j <= tot; j = -~j)
sz[j] = 0; for (register int j = 0; j < fa[i].size(); j = -~j) {
++sz[fa[i][j]];
} for (int u = dfncnt; u >= 1; --u) {
if (Node[u] != rt)
sz[fail[Node[u]]] += sz[Node[u]];
} build(1, 1, n); for (register int j = 0; j < Q[i].size(); j = -~j) {
answer[Q[i][j].id] = query_mx(1, Q[i][j].l, Q[i][j].r, 1, n);
}
}
} for (register int i = 1; i <= q; i = -~i)
write(answer[i]), putchar('\n'); return 0;
}
/*
6 1
a
aaa
dedicatus
misaka
mikoto
mi
1 2 2
*/

P8571 题解的更多相关文章

  1. 2016 华南师大ACM校赛 SCNUCPC 非官方题解

    我要举报本次校赛出题人的消极出题!!! 官方题解请戳:http://3.scnuacm2015.sinaapp.com/?p=89(其实就是一堆代码没有题解) A. 树链剖分数据结构板题 题目大意:我 ...

  2. noip2016十连测题解

    以下代码为了阅读方便,省去以下头文件: #include <iostream> #include <stdio.h> #include <math.h> #incl ...

  3. BZOJ-2561-最小生成树 题解(最小割)

    2561: 最小生成树(题解) Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 1628  Solved: 786 传送门:http://www.lyd ...

  4. Codeforces Round #353 (Div. 2) ABCDE 题解 python

    Problems     # Name     A Infinite Sequence standard input/output 1 s, 256 MB    x3509 B Restoring P ...

  5. 哈尔滨理工大学ACM全国邀请赛(网络同步赛)题解

    题目链接 提交连接:http://acm-software.hrbust.edu.cn/problemset.php?page=5 1470-1482 只做出来四道比较水的题目,还需要加强中等题的训练 ...

  6. 2016ACM青岛区域赛题解

    A.Relic Discovery_hdu5982 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Jav ...

  7. poj1399 hoj1037 Direct Visibility 题解 (宽搜)

    http://poj.org/problem?id=1399 http://acm.hit.edu.cn/hoj/problem/view?id=1037 题意: 在一个最多200*200的minec ...

  8. 网络流n题 题解

    学会了网络流,就经常闲的没事儿刷网络流--于是乎来一发题解. 1. COGS2093 花园的守护之神 题意:给定一个带权无向图,问至少删除多少条边才能使得s-t最短路的长度变长. 用Dijkstra或 ...

  9. CF100965C题解..

    求方程 \[ \begin{array}\\ \sum_{i=1}^n x_i & \equiv & a_1 \pmod{p} \\ \sum_{i=1}^n x_i^2 & ...

  10. JSOI2016R3 瞎BB题解

    题意请看absi大爷的blog http://absi2011.is-programmer.com/posts/200920.html http://absi2011.is-programmer.co ...

随机推荐

  1. virtualbox Ubuntn配置多站点

    1.编辑站点文件: nano /etc/nginx/sites-available/default cd /etc/nginx/sites-available/  ls2. 把default的设置文件 ...

  2. $KMP$学习记

    <不浪漫罪名>--王杰 没有花 这刹那被破坏吗 无野火都会温暖吗 无烟花一起庆祝好吗 若爱恋 仿似戏剧那样假 如布景一切都美化 连相拥都参照主角吗 你说我未能定时 令你每天欢笑一次 我没说 ...

  3. 80x86汇编—80x86架构

    文章目录 计算机如何工作 存储器 逻辑地址到物理地址 寄存器 数据寄存器使用细节 其他知识点细节 堆栈Stack 标志寄存器 中断 汇编入门简单,深入难 使用8086架构进行学习,本章节如果没有学过计 ...

  4. 关于Nacos身份认证绕过漏洞默认密钥和JWT的研究

    前言 由于本人的一个习惯,每次遇到漏洞并复现后都要编写poc,以便下一次的直接利用与复测使用.研究Nacos默认密钥和JWT的爱恨情仇的过程中遇到了莫名其妙的问题,在此做以记录,方便日后有大佬遇到相同 ...

  5. 免费考AI OCP认证,附通关秘籍!

    这是一个能让你快速熟悉AI相关技能的考试,由Oracle官方提供,而且限时免费. 它就是OCI Generative AI Professional. 可以看到,目前免费政策正在执行,到今年的7月31 ...

  6. 提升WordPress网站加载速度的8个小技巧

    提升WordPress网站加载速度是至关重要的,它不仅可以提高用户体验,还有助于SEO排名.以下是提升WordPress网站加载速度的8个小技巧,希望能帮助到大家. 优化图片: 使用适当大小和格式的图 ...

  7. python 实现限流

    固定窗口 固定窗口就是记录一个固定的时间窗口内的操作次数,操作次数超过阈值则进行限流. def fix_window_limit(redis_obj, period, max_count): &quo ...

  8. c# 32位程序突破2G内存限制

    起因在开发过程中,由于某些COM组件只能在32位程序下运行,程序不得不在X86平台下生成.而X86的32位程序默认内存大小被限制在2G.由于程序中可能存在大数量处理,期间对象若没有及时释放或则回收,内 ...

  9. 适用于linux的bilibiliB站直播间弹幕爬虫脚本

    适用于linux的bilibiliB站直播间弹幕爬虫脚本,命令行运行之,输入到命令行,部分内容参考自网络,代码底部可见原始代码出处 BUFF:然而,经测试,每次爬只能读取10条弹幕记录,这就使得在(s ...

  10. Linux设备驱动--异步通知

    注:本文是<Linux设备驱动开发详解:基于最新的Linux 4.0内核 by 宋宝华 >一书学习的笔记,大部分内容为书籍中的内容. 书籍可直接在微信读书中查看:Linux设备驱动开发详解 ...