既然字符串的总长一定,不妨对于每个询问中的 \(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. linux文本三剑客之grep及正则表达式详解

    linux文本三剑客之grep及正则表达式详解 目录 linux文本三剑客之grep及正则表达式详解 1. grep命令详解 2. 正则表达式 2.1 基本正则表达式 2.2 扩展正则表达式 1. g ...

  2. C#TMS系统学习(BaseCity页面)

    C#TMS系统代码-基础页面BaseCity学习 本人纯新手,刚进公司跟领导报道,我说我是java全栈,他问我会不会C#,我说大学学过,他说这个TMS系统就给你来管了.外包已经把代码给我了,这几天先把 ...

  3. JDK源码阅读-------自学笔记(七)(二维数组的浅析)

    实际开发中一般最多使用到二维数组,再高很少使用 二维数组很少用,实际开发中会使用容器代替使用 1.创建二维数组 1 // 二维数组初始化 2 int[][] secondDimensional = n ...

  4. PyQt5自定义信号

    一.简介 在 PyQt5 中,自定义信号是一个常见的任务,通常用于在对象之间传递信息或触发特定行为.自定义信号需要继承自 QtCore.pyqtSignal 并定义其参数类型. 二.操作步骤 1.导入 ...

  5. Linux:查看磁盘配额报告数据

    近期在超算云上跑代码遇到了个乌龙,就是作业一提交到集群上去先是PD状态,然后马上就终止调了.后来知道是我用户目录下文件数量太多,导致已经超过管理员给我分配的磁盘配额了(众所周知机器学习相关项目的数据集 ...

  6. Java 中的深拷贝和浅拷贝你了解吗?

    前言 Java 开发中,对象拷贝是常有的事,很多人可能搞不清到底是拷贝了引用还是拷贝了对象.本文将详细介绍相关知识,让你充分理解 Java 拷贝. 一.对象是如何存储的? 方法执行过程中,方法体中的数 ...

  7. Asp-Net-Core开发笔记:使用原生的接口限流功能

    前言 之前介绍过使用 AspNetCoreRateLimit 组件来实现接口限流 从 .Net7 开始,AspNetCore 开始内置限流组件,当时我们的项目还在 .Net6 所以只能用第三方的 现在 ...

  8. 如何从0-1了解 熟悉 精通gitlab

    加入gitlab团队项目: 打开其他用户极狐邀请邮件: 点击接受紫色邀请按钮"accept invitation": 选择免费试用90天saas服务: 使用邮箱注册进行邮箱验证[验 ...

  9. 【知识点】深入浅出STL标准模板库

    前几天谈论了许多关于数论和数据结构的东西,这些内容可能对初学者而言比较晦涩难懂(毕竟是属于初高等算法/数据结构的范畴了).今天打算来讲一些简单的内容 - STL 标准模板库. STL 标准模板库 C+ ...

  10. Android 13 - Media框架(21)- ACodec(三)

    关注公众号免费阅读全文,进入音视频开发技术分享群! 这一节我们一起来了解 ACodec 是如何通过 configureCodec 方法配置 OMX 组件的,因为 configureCodec 代码比较 ...