字符串算法

字符串为什么不滚出OI

manacher算法

可以做到 \(O(n)\) 复杂度求回文。
回文这种东西非常EX,当然可以用字符串哈希来解决这种EX的东西,但是,字符串哈希能做的操作属实有点少,虽说不容易被卡,但是好像用处不算太大。所以就有了 \(\text{manacher}\) 算法,在求回文的时候同时也可以解决多种复杂的问题。
原理呢也不太难。

(1)统计奇偶

首先就是统计奇偶,然后再在各个字符之间插入特殊符号,一般就是插 ‘#’字符,然后为了省略边界判断,在开头的时候插入一个‘@’字符(有\(\text{Letax}\) 美元符号不好写)。
例如: ababac 变为 @#a#b#a#b#a#c# 。

这样所有的回文串的长度都变为奇数。而原回文串的长度为现在回文串半径减 \(1\)
如: @#a#b#a#b#a#c#
半径长度->\(\text{11214161412121}\)
回文串长度->\(\text{00103050301010}\)

(2)求半径长度

那么半径长度怎么求呢?
我们设一个储存半径的数组 \(len_i\),求这个数组即可。设 \(mx\)\(\max \{ k+len_k - 1 \mid 1\leq k < i \}\),也就是前面回文串覆盖到的最远距离,然后设它的对称中心为 \(id\)(可能是原串中的字符,也可能是插入的字符)。这时 \(i\) 有它关于 \(id\) 的对称点 \(j\),这时可以发现,此时的 \(len_j\) 已经求出,那么如何确定 \(len_i\) 呢?
因为有 \(S_{mx' \sim mx}\) 关于 \(id\) 对称,而 \(S_{j-len_{j} \cdots j+len_{j}-1}\) 也关于 \(j\) 点对称,要求关于 \(i\) 点对称的半径,需要对 \(len_j\) 进行分类讨论。

如果 \(mx>i\),则有以下两种情况

情况一:\(i+len_j-1<mx\),则 \(len_i=len_j\)

如下图所示

image

情况二:\(i+len_j-1 \geq mx\),则 \(len_i=mx-i+1\),然后依次枚举判断 \(S_{mx+1\cdots} = S_{2*i-mx-1\cdots}\)。(注意 \(S_{mx+1\cdots}\) 是增大的,而 \(S_{2*i-mx-1\cdots}\) 是减小的)

如下图所示

image

如果 \(mx \leq i\)

则将 \(len_i=1\),然后依次枚举判断 \(S_{i+1 \cdots }=S_{i-1 \cdots }\)。(注意 \(S_{i+1 \cdots}\) 是增大的,而 \(S_{i-1\cdots}\) 是减小的)

(3)code

理解了思路代码就非常好写了。


char s[N*2],str[N*2];
int Len[N*2],len;
void init()
{
    int k=0;
    str[k++]='$';
    for(int i=0;i<len;i++)
        str[k++]='#',
        str[k++]=s[i];
    str[k++]='#';
    len=k;
}
void manacher()
{
    init();
    int mx=0,id;
    for(int i=1;i<len;i++)
    {
        if(mx>i) Len[i]=min(Len[2*id-i],mx-i);
        else Len[i]=1;
        while(str[i+Len[i]]==str[i-Len[i]]) 
            Len[i]++;
        if(Len[i]+i>mx)
            mx=Len[i]+i,id=i;
    }
}

题目

BZOJ 3790

母亲节就要到了,小 H 准备送给她一个特殊的项链。这个项链可以看作一个用小写字母组成的字符串,每个小写字母表示一种颜色。为了制作这个项链,小 H 购买了两个机器。第一个机器可以生成所有形式的回文串,第二个机器可以把两个回文串连接起来,而且第二个机器还有一个特殊的性质:假如一个字符串的后缀和一个字符串的前缀是完全相同的,那么可以将这个重复部分重叠。例如:\(\text{aba}\)\(\text{aca}\) 连接起来,可以生成串 \(\text{abaaca}\)\(\text{abaca}\)。现在给出目标项链的样式,询问你需要使用第二个机器多少次才能生成这个特殊的项链。

一遍manacher跑完之后就直接成了贪心经典题,区间覆盖了,为图省事直接扔到堆里了。

#include<bits/stdc++.h>
using namespace std;
const int N=4e5+1;
struct node
{
	int l,r;	
};
int len[N],id,mx,ans;
char s[N],s1[N];
bool operator < (node x,node y)
{
	return x.l>y.l;
}
priority_queue<node> q;
int main()
{
	while(scanf("%s",s)!=EOF)
	{
		memset(s1,0,sizeof(s1));
		s1[0]='$';
		for(int i=0;s[i];++i)
		{
			s1[i*2+1]='#';
			s1[i*2+2]=s[i];
		}
		s1[strlen(s1)]='#';
		memset(len,0,sizeof(len));
		ans=mx=id=0;
		for(int i=1;s1[i];++i)
		{
			len[i]=mx>i?min(len[id*2-i],mx-i):1;
			while(s1[i+len[i]]==s1[i-len[i]]) len[i]++;
			if(len[i]+i>mx) mx=len[i]+i,id=i;
		}
		while(!q.empty()) q.pop(); 
		int Len=strlen(s1)-1;
		for(int i=2;i<Len;i++) q.push((node){i-len[i]+1,i+len[i]-1});
		for(int L=1,R=0;L<Len;ans++,L=R)
			while(!q.empty()&&q.top().l<=L) 
			{
				R=max(R,q.top().r);
				q.pop();
			}
		printf("%d\n",ans-1);
	}
	return 0;
}

HDU 4513

 吉哥又想出了一个新的完美队形游戏!
 假设有 \(n\) 个人按顺序站在他的面前,他们的身高分别是 \(h_1 , h_2 \cdots ,h_n\),吉哥希望从中挑出一些人,让这些人形成一个新的队形,新的队形若满足以下三点要求,则就是新的完美队形:

  1、挑出的人保持原队形的相对顺序不变,且必须都是在原队形中连续的;
  2、左右对称,假设有 \(m\) 个人形成新的队形,则第 \(1\) 个人和第 \(m\) 个人身高相同,第 \(2\) 个人和第 \(m-1\) 个人身高相同,依此类推,当然如果 \(m\) 是奇数,中间那个人可以任意;
  3、从左到中间那个人,身高需保证不下降,如果用H表示新队形的高度,则 \(H_1 \leq H_2 \leq H_3 \cdots \leq H_mid\)

  现在吉哥想知道:最多能选出多少人组成新的完美队形呢?

一般的 \(\text{manacher}\) 添加的是’#’,但是本题左半边的身高不递减,所以添加的应该是 \((h_i+h_{i+1})/2\),注意细节。处理后的第奇数个身高是添加上去的,第偶数个身高是一开始输入的,当 \(i-p_i\) 是奇数时,无论 \(h_{i-p_i}\)\(h_{i+p_i}\) 是否相等,\(p_i\) 都应该加一。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
const int N=2e5+1;
int a[N],s[N],len[N];
int manacher(int l)
{
	int mx,id,ans;
	id=mx=ans=0;
	for(int i=0;i<l;i++)
	{
		if(i<mx) len[i]=min(len[2*id-i],mx-i);
		else len[i]=1;
		while(s[i-len[i]]==s[i+len[i]]) 
		{
			if(s[i-len[i]]==-1) len[i]++;
			else
			{
				if(s[i-len[i]]<=s[i-len[i]+2]) len[i]++;
				else break;
			}
		}
		if(i+len[i]>mx)
		{
			id=i;
			mx=i+len[i];
		}
		ans=max(ans,len[i]-1);
	}	
	return ans;
} 
int main()
{
	int n,t,L;
	cin>>t;
	while(t--)
	{
		scanf("%d",&n);
		for(int i=0;i<n;++i)
		{
			scanf("%d",&a[i]);
		}
		s[0]=0,s[1]=-1,L=2;
		for(int i=0;i<n;++i)
		{
			s[L++]=a[i];
			s[L++]=-1;
		}
		s[L++]=-1;
		printf("%d\n",manacher(L));
	}
	return 0;
}

HDU不让用万能头,差评!

trie树

trie树,又叫字典树,具体用处是啥呢?

posted @ 2023-01-09 11:20  离弦  阅读(32)  评论(0编辑  收藏  举报