P8571 题解
既然字符串的总长一定,不妨对于每个询问中的 \(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 题解的更多相关文章
- 2016 华南师大ACM校赛 SCNUCPC 非官方题解
我要举报本次校赛出题人的消极出题!!! 官方题解请戳:http://3.scnuacm2015.sinaapp.com/?p=89(其实就是一堆代码没有题解) A. 树链剖分数据结构板题 题目大意:我 ...
- noip2016十连测题解
以下代码为了阅读方便,省去以下头文件: #include <iostream> #include <stdio.h> #include <math.h> #incl ...
- BZOJ-2561-最小生成树 题解(最小割)
2561: 最小生成树(题解) Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 1628 Solved: 786 传送门:http://www.lyd ...
- Codeforces Round #353 (Div. 2) ABCDE 题解 python
Problems # Name A Infinite Sequence standard input/output 1 s, 256 MB x3509 B Restoring P ...
- 哈尔滨理工大学ACM全国邀请赛(网络同步赛)题解
题目链接 提交连接:http://acm-software.hrbust.edu.cn/problemset.php?page=5 1470-1482 只做出来四道比较水的题目,还需要加强中等题的训练 ...
- 2016ACM青岛区域赛题解
A.Relic Discovery_hdu5982 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/65536 K (Jav ...
- poj1399 hoj1037 Direct Visibility 题解 (宽搜)
http://poj.org/problem?id=1399 http://acm.hit.edu.cn/hoj/problem/view?id=1037 题意: 在一个最多200*200的minec ...
- 网络流n题 题解
学会了网络流,就经常闲的没事儿刷网络流--于是乎来一发题解. 1. COGS2093 花园的守护之神 题意:给定一个带权无向图,问至少删除多少条边才能使得s-t最短路的长度变长. 用Dijkstra或 ...
- CF100965C题解..
求方程 \[ \begin{array}\\ \sum_{i=1}^n x_i & \equiv & a_1 \pmod{p} \\ \sum_{i=1}^n x_i^2 & ...
- JSOI2016R3 瞎BB题解
题意请看absi大爷的blog http://absi2011.is-programmer.com/posts/200920.html http://absi2011.is-programmer.co ...
随机推荐
- 技术书籍 — EffectiveMordenCpp 研读
一.类型推导 PROs: 源码某处的类型修改,可以自动传播其他地方 Cons: 会让代码更复杂(How?) 在模板类型推导时,有引用的实参会被视为无引用,他们的引用会被忽略 template<t ...
- 每天上一当系列之vue修饰符.number
今天使用number修饰符去处理el-input的内容为数字做校验原本以为省事不少,没想到,为0开头无法输入第二位以后,并且输入的比较多的时候会出现Infinity 很神奇,网上查了说是element ...
- 什么是Java线程池
摘自:某个百度知道 多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力. 假设一个服务器完成一项任务所需时间为:T1 创建线程时间,T2 在 ...
- vue学习笔记之父组件子组件的传值
一 在前端开发过程中,很多情况下一个页面无法装载大部分的代码,所以需要子组件来完成父组件的任务,下面我来展示一下,组件之间如何进行传值以及常见的坑,首先上代码 1.1 父组件代码 <tem ...
- 将大量文件的拓展名中大写字母改为小写:Python实现
本文介绍基于Python语言,基于一个大文件夹,遍历其中的多个子文件夹,对于每一个子文件夹中的大量文件,批量将其文件的名称或后缀名中的字母由大写修改为小写的方法. 本文期望实现的需求为:现有一 ...
- P1036 [NOIP2002 普及组] 选数
传送锚点:https://www.luogu.com.cn/problem/P1036 题目描述 已知 \(n\) 个整数 \(x_1,x_2,\cdots,x_n\),以及 \(1\) 个整数 \( ...
- volatile关键字到底有什么作用
提示:更多优秀博文请移步博主的GitHub仓库:GitHub学习笔记.Gitee学习笔记 volatile是Java提供的一种轻量级的同步机制.Java 语言包含两种内在的同步机制:同步块(或方法)和 ...
- 自动化测试在 Kubernetes Operator 开发中的应用:以 OpenTelemetry 为例
背景 最近在给 opentelemetry-operator提交一个标签选择器的功能时,因为当时修改的函数是私有的,无法添加单测函数,所以社区建议我补充一个 e2e test. 因为在当前的版本下,只 ...
- Hugging Face x LangChain: 全新 LangChain 合作伙伴包
我们很高兴官宣发布 langchain_huggingface ,这是一个由 Hugging Face 和 LangChain 共同维护的 LangChain 合作伙伴包.这个新的 Python 包旨 ...
- 前端项目npm install安装报错:code ERESOLVE ERESOLVE could not resolve
背景:使用npm 安装依赖的时候,发现报了如下的错误: npm ERR! code ERESOLVE npm ERR! ERESOLVE unable to resolve dependency tr ...