• 「Codeforces 1202E」You Are Given Some Strings...


    Description

    定义, (f(T, s)) 为字符串 (s) 在字符串 (T) 中出现的次数,如:(f( exttt{"aaabacaa"}, exttt{"aa"}) = 3, f( exttt{"ababa"}, exttt{"aba"}) = 2)

    给定一个字符串 (t), (n) 个字符串 (s_1, s_2, cdots, s_n)。求

    [sumlimits_{i = 1}^n sumlimits_{j = 1}^n f(t, s_i + s_j) ]

    的值,其中 (s_i + s_j) 表示将字符串 (s_i, s_j) 拼接在一起。

    Hint

    • (1le |t| le 2 imes 10^5)
    • (1le nle 2 imes 10^5)
    • (1le sum_{i = 1}^{n} |s_i| le 2 imes 10^5)

    Solution

    注:下文中 (s[lcdots r]) 表示字符串 (s) 的第 (l) 到第 (r) 位形成的子串。

    (f_k) 表示有几个 ({s_1, s_2,cdots s_n}) 中的字符串是 (t[1cdots k]) 的后缀 ,设 (g_k) 表示有几个 ({s_1, s_2,cdots s_n}) 中的字符串是 (t[kcdots |t|]) 的前缀。

    若我们确定一个断点 (x),将 (t) 分为前后两部分 (t_1 = t[1cdots x], t_2 = t[x + 1cdots |t|]) 。找有几个 ({s_1, s_2,cdots s_n}) 中的字符串可以拼在 (t_1) 的后面,有几个可以拼在 (t_2) 的前面,将这两个个数一乘(乘法原理),就是这个断点对答案的贡献。

    于是答案就是:

    [sumlimits_{i = 1}^{n - 1} f_i imes g_{i + 1} ]


    接下来就是如何求 (f, g) 了。

    先建 AC自动机 。以 (f) 为例, (g) 反转后同理。

    当文本串 (t) 在自动机上走的时候,每走到一个结点,都要往 fail 指针的一直方向跳,沿途记录有几个模式串的结尾作为这一位的答案。但这样的复杂度有问题,如果遇到形如 aaaaa....aa 的就会被卡成 (O(sum |s_i| imes |t|)),绝对 T 飞。

    但是我们可以在建 fail 指针的时候就把这个结点 一直通过 fail 连到最上面有几个结尾点 处理出来就不会有这样的问题。具体地,只要在一个结点做好之后,加一句 t[x].cnt += t[t[x].fail].cnt; 即可,类似于前缀和。这样一直累加下去,最后 cnt 就从结尾的个数变成了一路跳 fail 的结尾的个数。

    时间复杂度:(O(sum|s_i| + |t|))

    Code

    /*
     * Author : _Wallace_
     * Source : https://www.cnblogs.com/-Wallace-/
     * Problem : Codeforces 1202E You Are Given Some Strings...
     */
    #include <algorithm>
    #include <iostream>
    #include <string>
    #include <queue>
    
    using namespace std;
    const int L = 2e5 + 5;
    const int S = 26;
    
    struct AC_Automaton {
    	struct ACAM_Node {
    		int ch[S]; // 子结点 
    		int fail; // fail 指针 
    		int cnt;
    		/* cnt 的前后两种含义 : 
    		 * 建fail前:结尾个数 
    		 * 建fail后:一路跳fail一共的结尾个数 
    		 */ 
    	} t[L];
    	int total;
    	
    	AC_Automaton() {
    		total = 0;
    	}
    	inline void 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++; // 结尾符标记 
    	}
    	inline void initFail() { //建 AC自动机 
    		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];
    			t[x].cnt += t[t[x].fail].cnt; // 在这里加一个前缀和的操作,当前的 cnt 加上 fail 对应的 cnt。 
    		}
    	}
    	inline void scan(int *f, string& s) { // 计算 f 和 g 
    		int x = 0;
    		for (string::iterator it = s.begin(); it != s.end(); it++, f++)
    			x = t[x].ch[*it - 'a'], *f = t[x].cnt;
    			// 预处理过,不需要暴跳fail了,直接取值。 
    	}
    } A, R; // 正AC自动机,反AC自动机 
    
    int f[L], g[L]; 
    signed main() {
    	ios::sync_with_stdio(false);
    	string s, t; int n;
    	
    	cin >> t >> n;
    	for (; n; --n) {
    		cin >> s;
    		A.insert(s);
    		reverse(s.begin(), s.end());
    		R.insert(s); // 反转后插入反自动机 
    	}
    	
    	A.initFail();
    	A.scan(f + 1, t);
    	
    	reverse(t.begin(), t.end());
    	R.initFail(); // 反转后在反自动机上跑 
    	R.scan(g + 1, t);
    	
    	long long ans = 0ll; // 不开 long long 见祖宗 
    	for (register int i = 1; i <= t.size(); i++)
    		ans += f[i] * 1ll * g[t.size() - i];
    		// 乘法原理,注意这里的 g 也反了。原来应为 ans += f[i] * 1ll * g[i + 1]
    	cout << ans << endl;
    	return 0;
    }
    
  • 相关阅读:
    DES、RC4、AES等加密算法优势及应用
    Jupyter Notebook 快捷键
    python 清华镜像pip install
    anaconda下载 (清华镜像)
    pycharm 授权/激活
    python序列中添加高斯噪声
    python 如何在一个.py文件中调用另一个.py文件的类
    pycharm 变量批量重命名
    pycharm常用快捷键
    C++中获取时间
  • 原文地址:https://www.cnblogs.com/-Wallace-/p/12915429.html
Copyright © 2020-2023  润新知