「模拟赛20180306」回忆树 memory LCA+KMP+AC自动机+树状数组
题目描述
回忆树是一棵树,树边上有小写字母。
一次回忆是这样的:你想起过往,触及心底……唔,不对,我们要说题目。
这题中我们认为回忆是这样的:给定 \(2\) 个点 \(u,v\) (\(u\) 可能等于 \(v\))和一个非空字符串 \(s\) ,问从 \(u\) 到 \(v\) 的简单路径上的所有边按照到 \(u\) 的距离从小到大的顺序排列后,询问边上的字符依次拼接形成的字符串中给定的串 \(s\) 出现了多少次。
输入
第一行 \(2\) 个整数,依次为树中点的个数 \(n\) 和回忆的次数 \(m\)。
接下来 \(n-1\) 行,每行 \(2\) 个整数 \(u,v\) 和 \(1\) 个小写字母 \(c\) ,表示回忆树的点\(u,v\)之间有一条边,边上的字符为\(c\)。
接下来 \(2m\) 行表示 \(m\) 次回忆,每次回忆 \(2\) 行:第 \(1\) 行 \(2\) 个整数 \(u,v\),第 \(2\) 行给出回忆的字符串 \(s\)。
输出
对于每次回忆,输出串 \(s\) 出现的次数。
样例
样例输入
12 3
1 2 w
2 3 w
3 4 x
4 5 w
5 6 w
6 7 x
7 8 w
8 9 w
9 10 x
10 11 w
11 12 w
1 7
wwx
1 12
www
1 12
w
样例输出
2
0
8
数据范围
\(1≤n,m≤10^5\)
询问字符串的总长度不超过\(3\times10^5\)
题解
这是一道神题,做法优美而且巧妙(同时也很恶心)。
既然是树链上的询问,就不能不让人想到利用\(LCA\)把\(u\xrightarrow{}v\)的路径转化成\(u\xrightarrow{}lca\),\(lca\xrightarrow{}v\)的两条路径了。
那么我们就可以把询问分成三部分。
- \(lca\xrightarrow{}u\)上\(s\)的反串出现了多少次
- \(lca\xrightarrow{}v\)上\(s\)出现了多少次
- 跨越\(lca\)时,\(s\)出现了多少次
可以发现,第一部分和第二部分其实是类似的问题,我们先放一放。
那么我们考虑第三个问题,好像没有什么很简单的方法,于是我们考虑暴力。
很容易发现这一种情况下涉及的字符串不长,只有\(u\xrightarrow{}lca\)路径上的\(\left|s\right|\)个和\(v\xrightarrow{}lca\)路径上的\(\left|s\right|\)个。我们可以暴力取出这一段字符,然后做一次\(KMP\),这样一次的复杂度是\(O(\left|s\right|)\),总时间复杂度就是\(O(\sum\left|s\right|)\),完全可以过。
现在就剩前两个问题了。我们发现询问串太多,一个个做显然很吃力,这时,\(AC\)自动机的方法就呼之欲出了。我们把所有询问串做成一个\(AC\)自动机,把整棵树带进去匹配即可。
匹配的过程很简单,模拟字符串匹配的时候即可,从根开始,依次访问子树,进栈的时候答案加,出栈的时候答案减即可,然后把询问的区间标记一下,到达合适的区间就计算答案。
但是这样还有一个问题,\(AC\)自动机上的答案是要给\(fail\)链上的所有点增加的,暴力加显然会超时。于是我们修改一下做法,预处理出\(fail\)树的先序遍历序列,然后建立树状数组(一个比较显然的性质,同一颗子树的遍历序列是连续的)。于是修改的时候单点修改,查询的时候查询\(fail\)树上的子树和即可。
然而,这道题说起来很轻巧,却是一道码农题……并且还卡常数……卡常数!!!
所以,我还是把我\(250\)行的代码拿出来吧……
\(Code:\)
#include <queue>
#include <vector>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define M 600005
queue<int>q;
int n, m;
int f[25][M], dep[M], fa[M];
int L[M], R[M], ans[M], ens[M], plc[M];
vector<int>B[M], E[M];
char len[M], top[M], S[M];
struct node
{
int fir[M], tar[M], nex[M], cnt;
}T1, T2;
void add(int a, int b, char c)
{
++T1.cnt;
T1.tar[T1.cnt] = b;
len[T1.cnt] = c;
T1.nex[T1.cnt] = T1.fir[a];
T1.fir[a] = T1.cnt;
}
void add(int a, int b)
{
++T2.cnt;
T2.tar[T2.cnt] = b;
T2.nex[T2.cnt] = T2.fir[a];
T2.fir[a] = T2.cnt;
}
//dfs-begin
void dfs(int r)
{
for (int i = T1.fir[r]; i; i = T1.nex[i])
{
int v = T1.tar[i];
if (v != fa[r])
{
fa[v] = r;
dep[v] = dep[r] + 1;
top[v] = len[i];
dfs(v);
}
}
}
//dfs-end
//LCA-begin
int LCA(int u, int v)
{
if (dep[u] < dep[v])
swap(u, v);
int k = dep[u] - dep[v];
for (int i = 20; i >= 0; i--)
if (k & 1 << i)
u = f[i][u];
if (u == v)
return u;
for (int i = 20; i >= 0; i--)
if (f[i][u] != f[i][v])
u = f[i][u], v = f[i][v];
return f[0][u];
}
int getk(int u, int k)
{
for (int i = 0; i <= 20; i++)
if (k & 1 << i)
u = f[i][u];
return u;
}
//LCA-end
//KMP-begin
char K[M];
int nex[M];
void KMP(int a, int b, int c, int ls, int w)
{
int len = 0;
while (a != c)
K[len++] = top[a], a = fa[a];
int z = dep[b] - dep[c];
len += dep[b] - dep[c];
while (b != c)
K[--len] = top[b], b = fa[b];
len += z;
K[len] = 0;
nex[0] = -1;
int i = 0, j = -1, ans = 0;
while(i < ls)
{
if (j == -1 || S[i] == S[j])
nex[++i] = ++j;
else
j = nex[j];
}
i = 0, j = 0;
while(i < len)
{
if (j == ls)
{
ans++;
j = nex[j];
continue;
}
if(j == -1 || K[i] == S[j])
i++, j++;
else
j = nex[j];
}
if (j == ls)
ans++;
ens[w] += ans;
}
//KMP-end
//ACTrie-begin
struct ACTrie
{
int nex[M][30], fail[M], in[M], out[M];
int root, cnt, tim, dfn[M], id[M];
int tree[M];
ACTrie(){root = cnt = 1;}
void Insert(char *S, int w)
{
int r = root, len = strlen(S);
for (int i = 0; i < len; i++)
{
int val = S[i] - 'a';
if (!nex[r][val])
nex[r][val] = ++cnt;
r = nex[r][val];
}
plc[w] = r;
}
void Build()
{
int r = root;
fail[r] = r;
q.push(root);
while (!q.empty())
{
r = q.front();
q.pop();
for (int i = 0; i < 26; i++)
{
if (nex[r][i])
{
int tmp = nex[fail[r]][i];
if (tmp && tmp != nex[r][i])
fail[nex[r][i]] = tmp;
else
fail[nex[r][i]] = root;
q.push(nex[r][i]);
}
else
{
int tmp = nex[fail[r]][i];
if (tmp)
nex[r][i] = tmp;
else
nex[r][i] = root;
}
}
if (r != root)
add(fail[r], r);
}
}
void DFS(int r)
{
dfn[r] = ++tim;
in[r] = tim;
id[tim] = r;
for (int i = T2.fir[r]; i; i = T2.nex[i])
{
int v = T2.tar[i];
DFS(v);
}
out[r] = tim;
}
void Update(int x, int v)
{
for (int i = x; i <= cnt; i += i & -i)
tree[i] += v;
}
int Getsum(int x)
{
int ans = 0;
for (int i = x; i; i -= i & -i)
ans += tree[i];
return ans;
}
}AC;
//ACTrie-end
void dfs2(int r, int now)
{
AC.Update(AC.dfn[now], 1);
int s = B[r].size();
for (int i = 0; i < s; i++)
ens[(B[r][i] + 1)/ 2] -= AC.Getsum(AC.out[plc[B[r][i]]]) - AC.Getsum(AC.in[plc[B[r][i]]] - 1);
s = E[r].size();
for (int i = 0; i < s; i++)
ens[(E[r][i] + 1)/ 2] += AC.Getsum(AC.out[plc[E[r][i]]]) - AC.Getsum(AC.in[plc[E[r][i]]] - 1);
for (int i = T1.fir[r]; i; i = T1.nex[i])
{
int v = T1.tar[i];
if (v != fa[r])
dfs2(v, AC.nex[now][len[i] - 'a']);
}
AC.Update(AC.dfn[now], -1);
}
int main()
{
//freopen("memory.in", "r", stdin);
//freopen("memory.out", "w", stdout);
scanf("%d%d", &n, &m);
for (int i = 1; i < n; i++)
{
int a, b;
char c[5];
scanf("%d%d%s", &a, &b, c);
add(a, b, c[0]);
add(b, a, c[0]);
}
dfs(1);
for (int i = 1; i <= n; i++)
f[0][i] = fa[i];
for (int i = 1; i <= 20; i++)
for (int j = 1; j <= n; j++)
f[i][j] = f[i - 1][f[i - 1][j]];
int w = 0;
for (int i = 1; i <= m; i++)
{
int u, v, c;
scanf("%d%d%s", &u, &v, S);
c = LCA(u, v);
int l1 = dep[u] - dep[c], l2 = dep[v] - dep[c], ls = strlen(S);
int a = getk(u, max(0, l1 - ls + 1));
int b = getk(v, max(0, l2 - ls + 1));
KMP(a, b, c, ls, i);
w++;
AC.Insert(S, w);
B[b].push_back(w);
E[v].push_back(w);
w++;
for (int i = 0; i < ls / 2; i++)
swap(S[i], S[ls - i - 1]);
AC.Insert(S, w);
B[a].push_back(w);
E[u].push_back(w);
}
AC.Build();
AC.DFS(AC.root);
dfs2(1, AC.root);
for (int i = 1; i <= m; i++)
printf("%d\n", ens[i]);
}
「模拟赛20180306」回忆树 memory LCA+KMP+AC自动机+树状数组的更多相关文章
- 「模拟赛20180307」三元组 exclaim 枚举+树状数组
题目描述 给定 \(n,k\) ,求有多少个三元组 \((a,b,c)\) 满足 \(1≤a≤b≤c≤n\)且\(a + b^2 ≡ c^3\ (mod\ k)\). 输入 多组数据,第一行数据组数\ ...
- 「模拟赛 2018-11-02」T3 老大 解题报告
老大 题目描述 因为 OB 今年拿下 4 块金牌,学校赞助扩建劳模办公室为劳模办公室群,为了体现 OI 的特色,办公室群被设计成了树形(n 个点 n − 1 条边的无向连通图),由于新建的办公室太大以 ...
- 「模拟赛20191019」C 推式子+贪心+树状数组
题目描述 给定一棵\(n\)个点的有根树,根节点编号为\(1\),点有点权. 定义\(d(v)\)表示\(v\)到\(1\)的路径上的边数. 定义\(f(v,u)\)在\(v<u\)且\(v\) ...
- 「模拟赛20180406」膜树 prufer编码+概率
题目描述 给定一个完全图,保证\(w_{u,v}=w_{v,u}\)且\(w_{u,u}=0\),等概率选取一个随机生成树,对于每一对\((u,v)\),求\(dis(u,v)\)的期望值对\(998 ...
- 「模拟赛20190327」 第二题 DP+决策单调性优化
题目描述 小火车虽然很穷,但是他还是得送礼物给妹子,所以他前往了二次元寻找不需要钱的礼物. 小火车准备玩玩二次元的游戏,游戏当然是在一个二维网格中展开的,网格大小是\(n\times m\)的,某些格 ...
- 「模拟赛20181025」御风剑术 博弈论+DP简单优化
题目描述 Yasuo 和Riven对一排\(n\)个假人开始练习.斩杀第\(i\)个假人会得到\(c_i\)个精粹.双方轮流出招,他们在练习中互相学习,所以他们的剑术越来越强.基于对方上一次斩杀的假人 ...
- 「模拟赛20191019」B 容斥原理+DP计数
题目描述 将\(n\times n\)的网格黑白染色,使得不存在任意一行.任意一列.任意一条大对角线的所有格子同色,求方案数对\(998244353\)取模的结果. 输入 一行一个整数\(n\). 输 ...
- 「模拟赛20191019」A 简单DP
题目描述 给一个\(n\times m\)的网格,每个格子上有一个小写字母. 对于所有从左上角\((1,1)\)到右下角\((n,m)\)只向下或向右走的路径构成的集合,判断是否存在两条走法不同的路径 ...
- 优先队列 + 并查集 + 字典树 + 欧拉回路 + 树状数组 + 线段树 + 线段树点更新 + KMP +AC自动机 + 扫描线
这里给出基本思想和实现代码 . 优先队列 : 曾经做过的一道例题 坦克大战 struct node { int x,y,step; friend bool operator <(no ...
随机推荐
- php 数字小写转为大写的函数
PHP把阿拉伯数字转换成中文,需要定义一个转换的算法: <?php /将数字转换为汉字,比如1210转换为一千二百一十 $num = "842105580";//九位数 fu ...
- JavaWEB - JSP 指令
- NYOJ-127 快速求幂,最小生成树
#include"iostream" using namespace std; int kuaisuqiumo(int a,int b,int c){ ; a = a % c; ) ...
- oracle 12c 多租户 pdb 恢复(单个pdb数据文件、非系统pdb表空间、整个pdb数据库)
环境:数据库版本 Oracle Database 12c Enterprise Edition Release 12.2.0.1.0 - 64bit Production 实验准备:1.-- 数据库归 ...
- WPF如何更改系统控件的默认高亮颜色 (Highlight brush)
我们在用WPF时, 经常会对系统控件的默认高亮等等颜色进行更改. 以前通常是用controlTemplate来实现. 今天发现一个更合理或者简单的方法: 用系统默认颜色的key, 比如 SystemC ...
- Spring boot 学习二:入门
1: 需要的环境: JDK:至少JDK7才支持Spring boot maven:至少3.2 spring-boot:1.2.5.RELEASE(在pom.xml中指定) 2: 创建一个maven工程 ...
- java基础知识(3)----面向对象
三:面向对象:特点: 1:将复杂的事情简单化.2:面向对象将以前的过程中的执行者,变成了指挥者.3:面向对象这种思想是符合现在人们思考习惯的一种思想. 过程和对象在我们的程序中是如何体现的呢?过程其实 ...
- ES6学习之函数扩展
函数默认参数 function test(x = 1, y = 2) { return x + y } test(5, 6) test() 若默认参数在必须参数之前,要想取得默认参数,只有当传入的值为 ...
- Visual Studio的输出窗口上输出调试信息的函数
Visual Studio的输出窗口上输出文字的函数 参考网站:http://www.voidcn.com/blog/u011808175/article/p-2083567.html 当你编写非控制 ...
- PCLVisualizer可视化类(4)
博客转载自:http://www.pclcn.org/study/shownews.php?lang=cn&id=168 多视口显示 所示,并进行比较分析,利用不同的搜索半径,基于同一点云计算 ...