zoukankan      html  css  js  c++  java
  • 【日常训练】简单的字符串

    Problem

    求长度为 \(n\) 的、字符集大小 \(5000\) 的串有多少个偶数长的子串前一半和后一半循环同构。
    \(n \leq 5000\)

    Solution

    分析

    问题即为求有多少个子区间可以表示成 \(uvvu\)\(|u|\neq 0\)\(|v|\) 可以为 \(0\))的形式。

    我们发现,如果以两个 \(v\) 作为中心,从中心分别向两边扩展,从这个过程可以得到两个字符串 \(a=v^Ru^R,b=vu\)\(u^R\) 表示 \(u\) 的反转),那么 \(s=a_1b_1a_2b_2\dots a_nb_n\) 这个串相当于是 \(v\)\(v^R\) 交错拼起来,\(u\)\(u^R\) 交错拼起来,然后再把这两个得到的字符串连起来。那么 \(s\) 这个串相当于两个偶回文串拼接而成。

    考虑枚举中心,然后得到向两边扩展的字符串 \(a,b\),两个字符串都只保留前缀 \(1\dots \min\{|a|,|b|\}\),然后根据上面的方式得到字符串 \(s\)。那么我们就是需要统计,\(s\) 有多少个前缀,能够表示成两个偶回文串拼接而成的结果(注意特判只有 \(|v|=0\))。

    Lemma 1: 对于一个双回文串 \(s\)(能够表示成两个非空回文串拼接的结果),若 \(s=x_1x_2=y_1y_2=z_1z_2(|x_1|<|y_1|<|z_1|)\)\(x_2,y_1,y_2,z_1\) 是回文串,则 \(x_1,z_2\) 也是回文串。

    证明: 下面只证 \(x_1\) 是回文串,\(z_2\) 同理。

    如上图,设 \(z_1=y_1v\),则 \(v\)\(y_2\) 的前缀,\(v^R\)\(x_2,y_2\) 的后缀,\(v\)\(x_2\) 的前缀,于是 \(x_1v\)\(z_1\) 的前缀。

    \(y_1\)\(z_1\) 的 border,所以 \(|v|\)\(z_1\) 的 period,于是 \(|v|\) 也是 \(x_1v\) 的 period。

    所以 \(x_1\)\(v^{\infty}\) 的后缀。

    \(v^R\)\(x_1,z_1\) 的前缀,而 \(|v|\)\(x_1\) 的 period,所以 \(x_1\)\(\left(v^R\right)^{\infty}\) 的前缀。

    \(x_1\) 是回文串。\(\square\)

    Lemma 2: 对于一个双回文串 \(s\),存在一种回文划分 \(s=ab\)\(a,b\) 均为回文串且非空),使得 \(a\)\(s\) 的最长回文前缀,或 \(b\)\(s\) 的最长回文后缀。

    可以根据 Lemma 1 加上一些分类得到,这里不详细证明。

    Lemma 3: 对于一个双偶回文串 \(s\)(能表示成两个偶回文串拼接的结果),存在一种回文划分 \(s=ab\)\(a,b\) 均为偶回文串且非空),使得 \(a\)\(s\) 的最长回文前缀,或 \(b\)\(s\) 的最长回文后缀。

    将 Lemma 1 和 Lemma 2 的「回文串」改成「偶回文串」结论仍然成立。

    Manacher 做法

    因此我们只需要求出所有前缀的最长偶回文前缀和最长偶回文后缀,并分别判断剩下的部分是否回文。

    求所有前缀的最长偶回文后缀,可以从左往右扫,维护一个当前的回文串能延伸右边界 \(rit\)。每次枚举到新的回文中心 \(i\),只需要更新左端点在区间 \((rit,i+r_i)\) 内的信息即可(\(r_i\) 表示 \(i\) 的回文半径)。正确性显然。(注意因为我们只考虑偶回文串,所以我们只枚举以特殊字符 #(Manacher 时插入的特殊字符)为回文中心的贡献)

    判断剩下的部分是否回文就直接用中心的回文半径判即可。最长偶回文前缀就枚举的时候顺便维护一下即可。

    #include <bits/stdc++.h>
    
    template <class T>
    inline void read(T &x)
    {
    	static char ch; 
    	while (!isdigit(ch = getchar())); 
    	x = ch - '0'; 
    	while (isdigit(ch = getchar()))
    		x = x * 10 + ch - '0'; 
    }
    
    template <class T>
    inline void relax(T &x, const T &y)
    {
    	if (x < y)
    		x = y; 
    }
    
    template <class T>
    inline void tense(T &x, const T &y)
    {
    	if (x > y)
    		x = y; 
    }
    
    const int MaxN = 1e4 + 5; 
    
    int n, m, ans; 
    int a[MaxN], s[MaxN]; 
    
    inline void solve(int *s, int n)
    {
    	static int t[MaxN], r[MaxN], m; 
    	static int max_suf[MaxN]; 
    	m = 0;
    
    	for (int i = 1; i <= n; ++i)
    	{
    		t[(i << 1) - 1] = 0; 
    		t[i << 1] = s[i]; 
    	}
    
    	m = n << 1 | 1, t[m] = 0; 
    	t[0] = -1, t[m + 1] = -2; 
    
    	int rit = 0, p = 0; 
    	for (int i = 1; i <= m; ++i)
    	{
    		if (rit > i)
    		{
    			int j = (p << 1) - i; 
    			r[i] = std::min(r[j], rit - i); 
    		}
    		else
    			r[i] = 1; 
    		while (t[i - r[i]] == t[i + r[i]])
    			++r[i]; 
    		if (i + r[i] > rit)
    		{
    			rit = i + r[i]; 
    			p = i; 
    		}
    		max_suf[i] = 0; 
    	}
    
    	rit = 1; 
    	for (int i = 1; i <= m; i += 2)
    	{
    		for (int j = i + r[i] - 1; j >= rit && j > i; --j)
    			relax(max_suf[j], (j - i) << 1 | 1); 
    		relax(rit, i + r[i]); 
    	}
    
    	int cur_pre = 0; 
    	for (int i = 2; i <= n; i += 2)
    	{
    		bool flg = false; 
    
    		if (r[i + 1] > i)
    		{
    			flg = true; 
    			cur_pre = i + 1;
    		} 
    		if (max_suf[i << 1])
    		{
    			int cur = i - (max_suf[i << 1] >> 1) - 1; 
    			if (r[cur + 1] > cur)
    				flg = true; 
    		}
    		if (cur_pre && r[cur_pre + i] > i - cur_pre)
    			flg = true; 
    		ans += flg; 
    	}
    }
    
    int main()
    {
    	freopen("naive.in", "r", stdin); 
    	freopen("naive.out", "w", stdout); 
    
    	read(n);
    	for (int i = 1; i <= n; ++i)
    		read(a[i]); 
    	for (int i = 1; i < n; ++i)
    	{
    		m = 0; 
    		int l = i, r = i + 1; 
    		while (l >= 1 && r <= n)
    		{
    			s[++m] = a[l]; 
    			s[++m] = a[r]; 
    			--l, ++r; 
    		}
    
    		solve(s, m); 
    	}
    	std::cout << ans << std::endl; 
    
    	return 0; 
    }
    

    回文树做法

    用 Hash 判回文。求每个前缀的最长偶回文前缀只要枚举的时候维护一下即可。

    然后求最长偶回文后缀只要在回文树上找到对应点的 fail 链上深度最大的偶回文串即可,这个也可以在构造回文树的时候顺带维护一下。

    (代码懒得写)

    参考文献

    1. WC2017 字符串算法选讲 —— 金策

    2. 《简单的字符串》题解 —— 钟子谦

  • 相关阅读:
    web通信浅析(上B/S交互)转载
    tomcat内部运行原理浅析转载
    oracle集合运算
    Oracle 游标使用全解
    oracle 一些基本概念
    1.搭建项目环境之TestDirector 8.0
    修改Win7远程桌面端口
    从第二份工作开始
    2.搭建项目环境之源代码管理SVN
    How to Get IIS Web Sites Information Programmatically
  • 原文地址:https://www.cnblogs.com/cyx0406/p/11938081.html
Copyright © 2011-2022 走看看