zoukankan      html  css  js  c++  java
  • 「Codeforces 163E」e-Government

    Description

    给定 (n) 个互不相同的字符串 (s_1, s_2,cdots s_n),一开始它们都包含在集合 (S) 中。

    接下来有 (k) 个操作:

    • ? 开头的操作为询问操作,给定字符串 (t),询问当前字符串集 (S) 中的每一个字符串匹配 (t) 的次数之和;
    • + 开头的操作为添加操作,表示将字符串 (s_i) 加入到集合中(如果已经加了则忽略此操作);
    • - 开头的操作为删除操作,表示将字符串 (s_i) 从集合中删除(如果 (S) 中本来就没有 (s_i) 则忽略此操作)。

    Hint

    • (1le n,kle 10^5)
    • (1le sum |s_i| le 10^6)

    Solution

    多模式串的匹配问题,考虑 AC自动机

    首先看看 AC自动机 是如何工作的:走到一个结点,从这个结点一直往 fail 方向跳,边跳边统计答案,累加答案后继续往下一个字母的方向走……

    这就是朴素的暴跳 fail 的方法。显然会被卡,跳一次 fail 都会卡成 (O(L)) ,这样一次询问下来就是 (O(L^2))


    接下来我们引入一个叫 fail树 的概念。如果一个结点 (x) (非根)的 fail 指向结点 (y),那么这个 fail树 上的结点 (x) 的父结点就是 (y)。最后建出的树即 fail树。因为每一个点只要一个 fail 边,根结点不算,所以边数等于结点数减一,保证是一颗合法的数。

    然后我们回回顾一下上面那个跳 fail 的过程,其实就是 在 fail树 上跳祖先的过程

    这样就好办了,fail 路径上求和变成了 fail树 的链上求和

    如果没有修改操作,那么直接预处理根到结点的点权(点权即以该点为结尾的字符串的个数)和即可。这样复杂度是 (O(sum|s_i| + sum|t_i|))


    但是很不幸有了修改操作,不能草率地前缀和。我们知道,如果不用前缀和,那么修改就是 fail树 上的单点修改。

    单点修改,链上查询……

    使用 轻重链剖分 + 树状数组 !复杂度 (O(sum|s_i| + sum|t_i| imes log^2(sum|t_i|))),虽然是两个 (log),但是树状数组常数小,树剖跑不满,所以还是毫不费力地过了(摊手)。

    但其实可以变成一个 (log),使用树上差分大法(wtcl不太会)。还有,可以使用神奇的 LCT 强行降成一个 (log)同时常数超级加倍最后只能极限卡常

    要注意,如果 AC自动机 的结点是从 0 开始的,记得树状数组那里加一,避免死循环。

    Code

    /*
     * Author : _Wallace_
     * Source : https://www.cnblogs.com/-Wallace-/
     * Problem : Codeforces 163E e-Government
     */
    #include <cstdlib>
    #include <iostream>
    #include <queue>
    #include <string>
    #include <vector>
    
    using namespace std;
    const int N = 1e5 + 5;
    const int L = 1e6 + 5;
    const int S = 26;
    
    struct AC_Automaton { // AC自动机 
    	struct Node {
    		int ch[S]; // 转移的边(自动机的转移函数)
    		int fail; // fail指针 
    		int cnt; // 以当前的结尾的字符串的个数 
    	} t[L];
    	int total; // 总结点数 (0-indexed) 
    	
    	inline int insert(string& s) { // 插入并返回结束位置 
    		int x = 0;
    		for (string::iterator it = s.begin(); it != s.end(); it++) {
    			int c = *it - 'a';
    			if (!t[x].ch[c]) t[x].ch[c] = ++total;
    			x = t[x].ch[c];
    		}
    		++t[x].cnt;
    		return x;
    	}
    	inline void initFail() { // AC自动机建立fail指针 
    		queue <int> Q;
    		for (register int c = 0; c < S; c++)
    			if (t[0].ch[c]) Q.push(t[0].ch[c]), t[t[0].ch[c]].fail = 0;
    		while (!Q.empty()) {
    			int x = Q.front();  Q.pop();
    			for (register int c = 0; c < S; c++)
    				if (t[x].ch[c]) {
    					Q.push(t[x].ch[c]);
    					t[t[x].ch[c]].fail = t[t[x].fail].ch[c];
    				} else t[x].ch[c] = t[t[x].fail].ch[c];
    		}
    	}
    } acam;
    
    int n, k;
    string str[N];
    int pos[N];
    bool inSet[N];
    
    namespace bit { // 树状数组 
    	long long t[L];
    	int size = 0;
    	#define lbt(x) (x & (-x))
    	inline void inc(int p, int v) { // 单点加 
    		for (; p <= size; p += lbt(p)) t[p] += v;
    	}
    	inline long long get(int p) { // 前缀和 
    		long long ret = 0;
    		for (; p; p -= lbt(p)) ret += t[p];
    		return ret;
    	}
    	inline long long get(int l, int r) { // 区间和 
    		return get(r) - get(l - 1);
    	}
    	#undef lbt
    }
    
    namespace Tree {
    	vector <int> G[L];	
    	int fa[L], size[L], dep[L];
    	int maxs[L], top[L];
    	int timer = 0, dfn[L];
    	
    	void __Dfs_1(int x) {
    		dep[x] = dep[fa[x]] + 1;
    		size[x] = 1, maxs[x] = 0;
    		for (vector <int>::iterator y = G[x].begin(); y != G[x].end(); y++) {
    			__Dfs_1(*y), size[x] += size[*y];
    			if (size[*y] > size[maxs[x]])
    				maxs[x] = *y;
    		}
    	}
    	void __Dfs_2(int x, int t) {
    		top[x] = t, dfn[x] = ++timer;
    		if (G[x].empty()) return;
    		__Dfs_2(maxs[x], t);
    		for (vector <int>::iterator y = G[x].begin(); y != G[x].end(); y++)
    			if (*y != maxs[x]) __Dfs_2(*y, *y);
    	}
    	
    	inline void getFailTree(AC_Automaton& acam) {
    		for (register int i = 1; i <= acam.total; i++) {
    			G[acam.t[i].fail + 1].push_back(i + 1);
    			fa[i + 1] = acam.t[i].fail + 1;
    		} // 建fail树 
    		bit::size = acam.total + 1;
    		__Dfs_1(1), __Dfs_2(1, 1); // 两个 DFS,处理树剖的信息 
    		for (register int i = 0; i <= acam.total; i++)
    			bit::inc(dfn[i + 1], acam.t[i].cnt); // 初始化树状数组的信息 
    	}
    	
    	inline long long getChain(int x) { // 在根到 x 的路径上求和 
    		long long ret = 0ll;
    		while (fa[top[x]]) {
    			ret += bit::get(dfn[top[x]], dfn[x]); // 累加答案 
    			x = fa[top[x]]; // 跳链 
    		}
    		ret += bit::get(dfn[x]); // 收尾 
    		return ret;
    	}
    };
    
    inline long long query(string& s) {
    	int x = 0; long long ret = 0ll;
    	for (string::iterator it = s.begin(); it != s.end(); it++) { // 一路走一遍 AC 自动机 
    		x = acam.t[x].ch[*it - 'a'];
    		ret += Tree::getChain(x + 1); // 树剖的链上求和 
    	}
    	return ret;
    }
    inline void add(int idx) {
    	if (inSet[idx]) return; // 注意如果已经在集合里了就忽略
    	inSet[idx] = true; // 标记 
    	bit::inc(Tree::dfn[pos[idx] + 1], +1); // 单点+1 
    }
    inline void del(int idx) {
    	if (!inSet[idx]) return;  // 注意如果本来就不在集合里就忽略
    	inSet[idx] = false; // 标记 
    	bit::inc(Tree::dfn[pos[idx] + 1], -1); // 单点-1 
    }
    
    signed main() {
    	ios::sync_with_stdio(false);
    	
    	cin >> k >> n;
    	for (register int i = 1; i <= n; i++) {
    		cin >> str[i];
    		pos[i] = acam.insert(str[i]); // 插入模式串,记录 AC自动机中的结束位置 
    		inSet[i] = true; // 一开始在集合里 
    	}
    	
    	acam.initFail(); // 处理 fail 指针 
    	Tree::getFailTree(acam); // 建fail树,树剖 
    	
    	while (k--) {
    		string cmd;
    		cin >> cmd;
    		char opt = *cmd.begin();
    		cmd.erase(cmd.begin());
    		
    		switch (opt) {
    			case '?' : // 询问 
    				cout << query(cmd) << '
    ';
    				break;
    			case '+' : // 单点+1 
    				add(atoi(cmd.c_str()));
    				break;
    			case '-' : // 单点-1 
    				del(atoi(cmd.c_str()));
    				break;
    		}
    	}
    }
    
  • 相关阅读:
    JMeter测试WEB性能入门
    Monkey测试运用实例
    Event percentages解析
    Monkey测试结果分析
    Monkey测试环境搭建
    Appium+java移动端项目测试问题整理
    appium定位安装包启动类名称
    Appium元素定位(uiautomatorviewer)
    Appium环境搭建(Windows版)
    Selenium+java项目测试问题整理
  • 原文地址:https://www.cnblogs.com/-Wallace-/p/12917078.html
Copyright © 2011-2022 走看看