• NOI2011 阿狸的打字机


    题目链接


    题解

    虽然总长度我们不能确定,但是如果跟着第一行字符串跑建 AC 自动机,节点数肯定 (le 10^5) 的,因为每个字符最多增加一个节点。

    考虑一个查询 ((x, y)), 即把字符串 (s[y]) 的每个点对应在图上的点 (+1),然后询问 (x) 对应节点的子树和。

    子树和想到 (dfs) 序差分 (+) 树状数组

    离线,把询问 (x) 打进 (d[y]) 里。 考虑对于每个 (y),先修改,然后询问对应的 (x)。这里的每个字符串既是匹配串也是模式串,所以 (s[y]) 对应的节点就是一条从根出发的链。第二次再跟着输入的第一行字符串跑 (AC) 自动机,动态维护将根节点到当前节点上的所有点 (+1),遇到一个 P 就停下来查询即可。

    注意 “B” 退回时,要删除对应的影响。

    时间复杂度

    (O((N + M)logN))

    小细节

    • 由于要按 B 回退,可以记录一个 (fa) 数组模拟往回跳。
    #include <iostream>
    #include <cstdio>
    #include <vector>
    #include <cstring>
    using namespace std;
    
    const int N = 100005;
    typedef long long LL;
    char s[N];
    int n, m, cnt, dfncnt, fail[N], match[N], fa[N], c[N], tr[N][26];
    int id[N], ans[N], dfn[N], sz[N], q[N], idx;
    
    // BIT
    void add(int x, int k) {
    	for (; x <= dfncnt; x += x & -x) c[x] += k;
    }
    
    int ask(int x) {
    	int res = 0;
    	for (; x; x -= x & -x) res += c[x];
    	return res;
    }
    
    // 询问 x 的子树和
    int query(int x) {
        return ask(dfn[x] + sz[x] - 1) - ask(dfn[x] - 1);
    }
    
    // 询问
    struct Q{
    	// 求 x 的子树和,给 ans[id] 的贡献是 +V, 
    	int x, id;
    };
    vector<Q> d[N]; // q[i] 表示插入恰好为 i 个字符串需要的询问
    
    // fail 树的边
    int head[N], numE = 0;
    struct E{
    	int next, v;
    } e[N];
    
    void addEdge(int u, int v) {
    	e[++numE] = (E) { head[u], v };
    	head[u] = numE;
    }
    
    // AC 自动机:插入
    void insert() {
    	int p = 0;
    	for (int i = 1; i <= n; i++) {
    		if (s[i] == 'B') p = fa[p];
     		else if (s[i] == 'P') id[i] = ++cnt, match[cnt] = p;
     		else {
     			int ch = s[i] - 'a';
     			if (!tr[p][ch])  tr[p][ch] = ++idx, fa[idx] = p;
     			p = tr[p][ch];
    		}
    	}
    }
    
    void dfs(int u) {
    	dfn[u] = ++dfncnt, sz[u] = 1;
    	for (int i = head[u]; i; i = e[i].next) {
    		int v = e[i].v;
    		dfs(v);
    		sz[u] += sz[v];
    	}
    }
    
    // 建 fail
    void build() {
    	int hh = 0, tt = -1;
    	for (int i = 0; i < 26; i++)
    		if (tr[0][i]) q[++tt] = tr[0][i];
    	while (hh <= tt) {
    		int u = q[hh++];
    		for (int i = 0; i < 26; i++) {
    			int v = tr[u][i];
    			if (v) {
    				fail[v] = tr[fail[u]][i];
    				q[++tt] = v;
    			} else tr[u][i] = tr[fail[u]][i];
    		}
    	}
    
    	for (int i = 1; i <= idx; i++) addEdge(fail[i], i);
    	dfs(0);
    }
    
    void work() {
    	int p = 0;
    	for (int i = 1; i <= n; i++) {
    		if (s[i] == 'B') {
    			add(dfn[p], -1);
    			p = fa[p];
     		} else if (s[i] == 'P') {
     			int x = id[i];
     			for (int j = 0; j < d[x].size(); j++) {
     				Q k = d[x][j];
     				ans[k.id] += query(match[k.x]);
     			}
     		} else {
     			p = tr[p][s[i] - 'a'];
     			add(dfn[p], 1);
     		}
    	}
    }
    
    int main() {
    	scanf("%s%d", s + 1, &m);
    	n = strlen(s + 1);
    	for (int i = 1, x, y; i <= m; i++) {
    		scanf("%d%d", &x, &y);
    		d[y].push_back((Q){ x, i });
    	}
     	insert();
     	build();
     	work();
    	for (int i = 1; i <= m; i++) printf("%d
    ", ans[i]);
    	return 0;
    }
    
  • 相关阅读:
    成员变量、类变量、局部变量的区别
    微服务学习笔记二:Eureka服务注册发现
    微服务学习笔记一:Spring Cloud简介
    Java集合篇六:Map中key值不可重复的测试
    重写Euqals & HashCode
    Java集合篇五:HashMap
    Java集合篇四:Map的基本应用
    Java集合篇三:Vector
    Java集合篇二:LinkList
    Java集合篇一:ArrayList
  • 原文地址:https://www.cnblogs.com/dmoransky/p/12421509.html
Copyright © 2020-2023  润新知