• LuoguP2414 [NOI2011]阿狸的打字机


    题意简述

    (n) 个字符串, 多次询问第 (x) 串在第 (y) 串中的出现次数.

    题目分析

    首先当然要打开题目. 然后看题.

    不难往字符串这方面想. 而多串匹配自然想到了建AC自动机.

    怎么建图

    很简单, 在 (Trie) 上遇到 ("B") 就返回父亲结点, 遇到 ("P") 就把当前结点标记为结束结点. 遇到小写字符往儿子走就行.

    从上面发现实现时还要记录每个点的父亲.

    转换模型

    考虑什么时候 (x) 串会是 (y) 串的子串. 实际上就是存在一个结点 (u)(Root) 结点到 (y) 串的结束路径上, 满足其跳 (Fail) 时会经过 (x) 串的结束结点.

    这时你可能还是一脸懵逼. 没事, 接着看下去吧!

    几个结论(性质)

    1. 对于一个字符串 (u) , 从 (Root) 结点到其结束结点所走过的路径都是其前缀.
    2. 一个结点 (v)(Fail) 及其 (Fail)(Fail) 都是 (v)后缀.
    3. 一个字符串 (u)前缀的后缀即是其子串.
    4. (Fail) 关系是一棵.

    思考结论

    现在可能有点头绪了. 继续看吧.

    (x) 串是 (y) 串的子串

    (<=>) (x) 串是 (y) 串前缀的后缀

    (<=>) (x) 串的结束结点会被 (Root) 结点到 (y) 串结束结点路径上的某个点跳 (Fail) 跳到.

    现在好像有点意思了.

    但题目要求的是出现次数, 如果对于每组询问都暴力跳 (y) 串每一个前缀的 (Fail) 的话时间恐怕要爆炸.

    换一种思考问题的角度:

    如果把 (Fail) 反向的话, 是不是就变成了 (x)逆向(Fail) 能跳到 (Root) 结点到 (y) 串结束结点路径上多少个点?

    并且貌似现在只要考虑 (Fail) 的关系...再联系一下上文的结论 (4.) ...

    可以把 (Fail) 提出来单独建树! 问题就变成了多次询问 (x) 的子树中包含 (Root) 结点到 (y) 结点路径上点的数目. 这个问题可以用 树上 (DFS) 转化为多次单点修改+区间查询.

    但是如果每次询问都独立处理恐怕还是有点慢...

    不急, 回想一下打字机工作的流程, ("B") 操作回到父亲, ("P") 操作获取新字符串, 小写字符则进入儿子. 好像 (DFS) 的过程啊... 而且每一个编号相邻字符串在 (DFS) 上的确定顺序也是连续的?

    到这里, 发现可以将询问离线然后按照 (y) 串编号升序排序, 然后直接模拟打字机的工作, 在 (Trie)(DFS), 维护一个 (BIT), 进入儿子就 (Add), 退出就(Del), 遇到结束结点就处理以其为 (y) 串的询问.

    至此, 本题完美解决.

    代码实现

    #include <cstdio>
    #include <queue>
    #include <cstring>
    #include <algorithm>
    
    const int N = 1e5;
    
    int n, nL;
    char S[N + 5];
    
    int m, Ans[N + 5];
    struct Query {int x, y, id;} Q[N + 5];
    
    int Head[N + 5], ECnt;
    struct Edge {int nxt, to;} E[N + 5];
    
    int Size[N + 5], Id[N + 5], ICnt;
    
    void Add (int, int);
    bool Cmp (Query, Query);
    void DFS (int);
    
    struct ACAM {
    	int Endpos[N + 5];
    	int Fa[N + 5], Cnt, Root;
    	int Trans[N + 5][26 + 5], Fail[N + 5];
    
    	void clear() {
    		Cnt = Root = 1;
    		for (int i = 0; i < 26; ++i) Trans[0][i] = Root;
    	
    	} void insert (char * Str) {
    		int tap, RCnt;
    		RCnt = 0, tap = Root, nL = strlen (Str);
    		for (int i = 0; i < nL; ++i) {
    			char tbp = Str[i];
    			if (tbp == 'B') tap = Fa[tap];
    			else if (tbp == 'P') Endpos[++RCnt] = tap;
    			else {
    				int &tcp = Trans[tap][tbp - 'a'];
    				if (!tcp) tcp = ++Cnt, Fa[tcp] = tap;
    				tap = tcp;
    			}
    		}
    	} void build () {
    		std::queue <int> Q;
    		for (int i = 0; i < 26; ++i) Trans[0][i] = Root;
    
    		Q.push (Root);
    		while (Q.size()) {
    			int tap = Q.front(); Q.pop();
    			for (int i = 0; i < 26; ++i) {
    				int &tbp = Trans[tap][i];
    				if (tbp) {
    					Q.push (tbp), Fail[tbp] = Trans[Fail[tap]][i];
    					Add (Trans[Fail[tap]][i], tbp);
    				} else tbp = Trans[Fail[tap]][i];
    			}
    		}
    	}
    } Zoe;
    
    struct BIT {
    	int c[N + 5];
    
    	int lowbit (int p) {return (p & (-p));
    
    	} void add (int p, int k) {
    		for (int i = p; i <= nL; i += lowbit (i)) c[i] += k;
    
    	} int query (int p) {
    		int tap = 0;
    		for (int i = p; i; i -= lowbit (i)) tap += c[i];
    		return tap;
    	}
    } Nico;
    
    int main() {
    	scanf ("%s%d", S, &m);
    	Zoe.clear(), Zoe.insert (S), Zoe.build(), DFS(Zoe.Root);
    	for (int i = 1; i <= m; ++i) Q[i].id = i, scanf ("%d%d", &Q[i].x, &Q[i].y);
    	std::sort (Q + 1, Q + 1 + m, Cmp);
    	
    	int Now, p, Tot;
    	Now = Zoe.Root, Tot = 0, p = 1;
    	for (int i = 0; i < nL; ++i) {
    		char tap = S[i];
    		if (tap == 'P') {
    			++Tot;
    			while (p <= m && Q[p].y < Tot) ++p;
    			while (p <= m && Q[p].y == Tot) {
    				int tbp = Zoe.Endpos[Q[p].x];
    				Ans[Q[p].id] = Nico.query (Id[tbp] + Size[tbp] - 1) - Nico.query (Id[tbp] - 1);
    				++p;
    			}
    		} else if (tap == 'B') Nico.add (Id[Now], -1), Now = Zoe.Fa[Now];
    		else Now = Zoe.Trans[Now][tap - 'a'], Nico.add (Id[Now], 1);
    	
    	} for (int i = 1; i <= m; ++i) printf ("%d
    ", Ans[i]);
    	return 0;
    
    } void Add (int u, int v) {
    	E[++ECnt] = (Edge) {Head[u], v}, Head[u] = ECnt;
    
    } bool Cmp (Query X, Query Y) {
    	return X.y < Y.y;
    
    } void DFS (int p) {
    	Id[p] = ++ICnt, Size[p] = 1;
    	for (int i = Head[p]; i; i = E[i].nxt)
    		DFS (E[i].to), Size[p] += Size[E[i].to];
    }
    
  • 相关阅读:
    上周热点回顾(5.9-5.15)团队
    上周热点回顾(5.2-5.8)团队
    上周热点回顾(4.25-5.1)团队
    .NET跨平台之旅:升级ASP.NET Core示例站点团队
    上周热点回顾(4.18-4.24)团队
    上周热点回顾(4.11-4.17)团队
    如何在Eclipse中正确安装Jetty插件并初步使用(图文详解)
    Spark Mllib里如何采用保序回归做回归分析(图文详解)
    机器学习概念之梯度下降算法(全量梯度下降算法、随机梯度下降算法、批量梯度下降算法)
    程序代码里出现illegal character 'ufeff' 和 expected class or object definition的解决办法(图文详解)
  • 原文地址:https://www.cnblogs.com/Rothen/p/13897367.html
Copyright © 2020-2023  润新知