既然字符串的总长一定,不妨对于每个询问中的 \(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. Tensorflow和飞桨Paddle的控制流算子设计

    一.概览 注:整体方案上尚存在技术疑点,需进一步小组内讨论对齐,避免方案设计上存在后期难以扩展(或解决)的局限性 框架 TensorFlow 1.x TensorFlow 2.x Paddle con ...

  2. Ubuntu下MPICH的安装与配置

    原创直达链接 一.MPICH的下载与安装 MPI安装文件下载地址: 博客下载地址 或 官网地址 可以下载3.4.2版本的,本文就是3.4.2版本 1.解压: sudo tar - zxvf mpich ...

  3. web页面打开直接调用vlc播放视频

    简介 大家都知道现在我们在网页所播放的视频都是h264编码格式,可以供所有设备正常播放.然而,相比h265它的体积更大.质量更差.目前h265大多应用于安防,体积小可以更好的存储,不过它也有着缺点,成 ...

  4. nginx 常见配置案例参考(优化)

    在NGINX中,可以通过配置文件和特定的指令来实现权限控制.以下是一些常见的权限控制方法: 使用deny指令: 在NGINX配置文件中,可以使用deny指令来拒绝特定IP地址或IP地址范围的访问.可以 ...

  5. 【题解】A18747.眼红的同学

    题目链接:眼红的同学 题干信息很简单,看到数据量之后就不简单了.在数据量小的时候可以使用双层循环暴力的方法来求答案.显然对于这道题而言O(n^2)是完全过不去的. 前置知识: 使用树状数组求逆序对 会 ...

  6. Django中的ORM转换为SQL语句日志

    如果想打印ORM转换过程中的SQL,需要在settings中进行如下配置: LOGGING = { 'version': 1, 'disable_existing_loggers': False, ' ...

  7. go 交叉编译遇到的错误, 有路由方法却找不到。

    panic: 'OrderCancel' method doesn't exist in the controller Controller今天线下能正常编译,到线上却panic了.发现是自己导入了i ...

  8. WPF 实现触摸滑动功能

    自定义ScrollViewer的Touch事件--触摸上下移动ScrollViewer滚动到指定位置   1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ...

  9. centos6 chkconfig的原理 和添加开机自启动的办法

    当我们使用 chkconfig --list的时候 都会又 123456 这样的级别. 当某个级别是 on 他就会开机启动,当他是off 的时候他就不会开机自启动. 那么这是什么原因呢?他的 原理是什 ...

  10. open代码学习

    ADC 用宏定义c++编译器兼容c程序 #ifdef __cplusplus extern "c" { } 枚举类型传值 typedef enum{ CHANNAL_1 = 1; ...