[CF587F]Duff is Mad[AC自动机+根号分治+分块]
题意
给你 \(n\) 个串 \(s_{1\cdots n}\) ,每次询问给出 \(l,r,k\) ,问在 \(s_{l\cdots r}\) 中出现了多少次 \(s_k\) 。
\(n,q,\sum|s|\le 10^5\)
分析
先建AC自动机的 \(fail\) 树, 我们考虑两种暴力:
- 将 \(l\) 到 \(r\) 中的每个串的末尾节点子树标记,查询 \(s_k\) 的所有节点 \(fail\) 树到根的路径和。
- 将 \(s_k\) 的每个节点的子树标记,查询 \(l\) 到 \(r\) 中的每个末尾节点的点权和。
发现这两种做法在不同的数据下有着不同的效果,考虑根号分治:
- 如果 \(|s_k|\le\sqrt n\) 采用第一种方式,差分查询,这样操作每个串的次数不超过 \(\sqrt n\) ,动态维护前缀和。
- 如果 \(|s_k|>\sqrt n\) 采用第二种方式,记录前缀和即可,这样的串不超过 \(\sqrt n\) 个。
我们发现,对于 \(dfs\) 序数组来说,修改次数是 \(O(n)\) 级别,但是查询次数却是 \(O(n\sqrt n)\) 级别的,能不能平衡两种操作时间复杂度呢?
考虑分块来维护前缀和,每个块维护一个加标记。这样修改变成了 \(O(\sqrt n)\) ,但是查询变成了 \(O(1)\) 。
总时间复杂度为 \(O(n\sqrt n)\)。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
#define go(u) for(int i = head[u], v = e[i].to; i; i=e[i].lst, v=e[i].to)
#define rep(i, a, b) for(int i = a; i <= b; ++i)
#define pb push_back
#define re(x) memset(x, 0, sizeof x)
inline int gi() {
int x = 0,f = 1;
char ch = getchar();
while(!isdigit(ch)) { if(ch == '-') f = -1; ch = getchar();}
while(isdigit(ch)) { x = (x << 3) + (x << 1) + ch - 48; ch = getchar();}
return x * f;
}
template <typename T> inline void Max(T &a, T b){if(a < b) a = b;}
template <typename T> inline void Min(T &a, T b){if(a > b) a = b;}
const int N = 1e5 + 7;
int n, q, sz = 317, tim;
int L[N], R[N], in[N], out[N], ch[N][26];
char s[N];
namespace tr{
int edc;
int head[N];
struct edge {
int lst, to;
edge(){}edge(int lst, int to):lst(lst), to(to) {}
}e[N];
void Add(int a, int b) {
e[++edc] = edge(head[a], b), head[a] = edc;
}
void dfs(int u) {
in[u] = ++tim; go(u) dfs(v); out[u] = tim;
}
}
namespace ac {
int endp[N], fail[N], ndc;
int idx(char c) { return c - 'a';}
void ins(int a) {
L[a] = R[a - 1] + 1;
scanf("%s", s + L[a]);
R[a] = L[a] + strlen(s + L[a]) - 1;
int u = 0;
for(int i = L[a]; i <= R[a]; ++i) {
int c = idx(s[i]);
if(!ch[u][c]) ch[u][c] = ++ndc;
u = ch[u][c];
}
endp[a] = u;
}
void getfail() {
queue<int>Q;
for(int c = 0; c < 26; ++c) if(ch[0][c]) Q.push(ch[0][c]), tr::Add(0, ch[0][c]);
while(!Q.empty()) {
int u = Q.front();Q.pop();
for(int c = 0; c < 26; ++c) {
int &v = ch[u][c];
if(!v) { v = ch[fail[u]][c]; continue;}
fail[v] = ch[fail[u]][c];
Q.push(v);
tr::Add(fail[v], v);
}
}
}
}
struct data {
int l, r, k, id, opt;
bool operator <(const data &rhs) const {
return r < rhs.r;
}
}t[N << 1];
vector<data>G[N];
int qc, bl[N];//时间戳数组长度为tim
int Rp(int x){ return min(tim, x * sz);}
LL ans[N], sum[N], pre[N], add[400];
void mdf(int p, int v) {
if(p == tim + 1) return;
for(int i = p; i <= Rp(bl[p]); ++i) pre[i] +=v;
for(int i = bl[p] + 1; i <= bl[tim]; ++i) add[i] += v;
}
LL qry(int p) { return pre[p] + add[bl[p]]; }
void modify(int l, int r) { mdf(l, 1); mdf(r + 1, -1);}
LL query(int l, int r) { return qry(r) - qry(l - 1); }
void solve(int x) {
re(sum), re(pre), re(add);
int u = 0;
for(int i = L[x]; i <= R[x]; ++i) {
u = ch[u][ac::idx(s[i])];
mdf(in[u], 1);
}
for(int i = 1; i <= n; ++i) sum[i] = sum[i - 1] + query(in[ac::endp[i]], out[ac::endp[i]]);
for(auto v: G[x]) {
ans[v.id] = sum[v.r] - sum[v.l - 1];
}
}
void Addstring(int x) {
int u = ac::endp[x];
modify(in[u], out[u]);
}
LL Substring(int x) {
int u = 0;LL ans = 0;
for(int i = L[x]; i <= R[x]; ++i) {
u = ch[u][ac::idx(s[i])];
ans += qry(in[u]);
}
return ans;
}
int main() {
n = gi(), q = gi();
rep(i, 1, n) ac::ins(i);
ac::getfail(), tr::dfs(0);
rep(i, 1, tim) bl[i] = (i - 1) / sz + 1;
rep(i, 1, q) {
int l = gi(), r = gi(), k = gi();
if(R[k] - L[k] + 1 <= sz) {
t[++qc] = (data){ 0, l - 1, k, i, -1 };
t[++qc] = (data){ 0, r, k, i, 1 };
}else
G[k].pb((data){ l, r, k, i, 1});
}
rep(i, 1, n) if(R[i] - L[i] + 1 > sz) solve(i);
re(pre), re(add);
sort(t + 1, t + 1 + qc);
for(int i = 0, j = 1; i <= n; ++i) {
if(i) Addstring(i);
for(; j <= qc && t[j].r == i; ++j) {
ans[t[j].id] += 1ll * t[j].opt * Substring(t[j].k);
}
}
rep(i, 1, q) printf("%lld\n", ans[i]);
return 0;
}
[CF587F]Duff is Mad[AC自动机+根号分治+分块]的更多相关文章
- 【CF587F】Duff is Mad AC自动机+分块
[CF587F]Duff is Mad 题意:给出n个串$s_1,s_2..s_n$,有q组询问,每次给出l,r,k,问你编号在[l,r]中的所有串在$s_k$中出现了多少次. $\sum|s_i|, ...
- CF587F-Duff is Mad【AC自动机,根号分治】
正题 题目链接:https://www.luogu.com.cn/problem/CF587F 题目大意 给出\(n\)个字符串\(s\).\(q\)次询问给出\(l,r,k\)要求输出\(s_{l. ...
- BZOJ.4320.[ShangHai2006]Homework(根号分治 分块)
BZOJ \(\mathbb{mod}\)一个数\(y\)的最小值,可以考虑枚举剩余系,也就是枚举区间\([0,y),[y,2y),[2y,3y)...\)中的最小值(求后缀最小值也一样)更新答案,复 ...
- CF587F Duff is Mad(AC自动机+树状数组+分块)
考虑两一个暴力 1 因为询问\([a,b]\)可以拆成\([1,b]\)-\([1,a-1]\)所以把询问离线,然后就是求\([1,x]\)中被\(S_i\)包含的串的数量.考虑当\([1,x-1]- ...
- CF587F Duff is Mad
题目 有趣的思想 首先暴力的话,自然是对每一个询问在\(AC\)自动机上跑一遍\(k\),看看跑出来的节点在\(fail\)树到根的路径上有多少个\(l\)到\(r\)之间的结束标记就好了 我们发现无 ...
- [CF1083F]The Fair Nut and Amusing Xor[差分+同余分类+根号分治+分块]
题意 给定两个长度为 \(n\) 的序列 \(\{a_i\}\) 与 \(\{b_i\}\),你需要求出它们的相似度.,我们定义这两个序列的相似度为将其中一个序列转化为另一个序列所需的最小操作次数.一 ...
- HDU4787 GRE Words Revenge【AC自动机 分块】
HDU4787 GRE Words Revenge 题意: \(N\)次操作,每次记录一个\(01\)串或者查询一个\(01\)串能匹配多少个记录的串,强制在线 题解: 在线的AC自动机,利用分块来降 ...
- Codeforces 587F - Duff is Mad(根号分治+AC 自动机+树状数组)
题面传送门 第一眼看成了 CF547E-- 话说 CF587F 和 CF547E 出题人一样欸--还有另一道 AC 自动机的题 CF696D 也是这位名叫 PrinceOfPersia 的出题人出的- ...
- NOI.AC#2266-Bacteria【根号分治,倍增】
正题 题目链接:http://noi.ac/problem/2266 题目大意 给出\(n\)个点的一棵树,有一些边上有中转站(边长度为\(2\),中间有一个中转站),否则就是边长为\(1\). \( ...
随机推荐
- 【SPL标准库专题(7)】 Datastructures:SplHeap & SplMaxHeap & SplMinHeap
堆(Heap)就是为了实现优先队列而设计的一种数据结构,它是通过构造二叉堆(二叉树的一种)实现.根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆.二叉堆还常用于排序(堆排序). 类摘 ...
- memcached编译安装报错 ,提示checking build system type... Invalid configuration `x86_64-unknown-linux-': machine `x86_64-unknown-linux' not recognized configure: error: /bin/sh ./config.sub x86_64-unknown-linu
- Azure 中的虚拟网络和虚拟机
创建 Azure 虚拟机 (VM) 时,必须创建虚拟网络 (VNet) 或使用现有的 VNet. 此外,还需要确定如何在 VNet 上访问 VM. 在创建资源之前必须做好规划,确保了解网络资源的限制. ...
- 巧用top percent优化top 1
废话不多说,直接上sql B.CREW_ID, E.CREW_NAME,C.OFFBLK,C.ONBLK,dbo.PEK_OPS_Date(A.STD) as STD FROM dbo.FLIGHTS ...
- InfoPath读取数据库
public void LoadBtn_Clicked(object sender, ClickedEventArgs e) { // 配置连接字符串 using (SqlConnection con ...
- cat > file << EOF 与 cat > file << -
当我们在使用kickstart 的时候,会遇到写网卡配置文件的情况,这时候我们使用cat > file << EOF 命令等,可以从标准输入中接受输入并保存到 file 文件中. c ...
- 详解Web请求中的DNS域名解析
当我们打开浏览器,输入一个URL去请求我们需要的资源,但是URL是需要解析成对应的IP地址才能与远程主机建立连接,如何将URL解析成IP就是DNS的工作范畴,即使作为开发人员,这个过程我们也感觉不到, ...
- python 爬取全量百度POI
在网上找了很多关于爬取百度POI的文章,但是对“全量”的做法并没有得到最终的解决方案,自己写了一个,但还是不能实现全量POI抓取,能够达到至少50%的信息抓取.注意:这里所指“全量”是能够达到100% ...
- python第三十八课——面向对象(一)
1.面向对象:(思想) 面向:看.关注.瞅 对象:个体.实体.实例.结果单词:object在python中一些皆对象 面向过程:(思想) 面向:看.关注.瞅 过程:经过.经历.从头到尾 使用一些生活中 ...
- linux添加磁盘空间
首先你要关掉系统,把分配的硬盘空间变大,或者重新建立一个虚拟硬盘(这时下面的就不是sda了,而是sdb1了).这两种方法都可行,我都试过了.其次用root用户登录到你的linux系统,查看你系统的分区 ...