manacher + 最小表示法 + 字符串哈希中二分

manacher 问题是为了解决最长回文子串的问题!!
这种题目维护的方式和exkmp一样也是维护一个区间,区间中的数

#include <bits/stdc++.h>
using namespace std;
int n,p[2*n+2];
char s[n+2],t[2*n+3];
void manacher()
{
	int len1=strlen(s+1);
	int len2=0;
	t[++len2]='$';
	for(int i=1;i<=len1;i++)
	{
		t[++len2]=s[i],
		t[++len2]='$';
	}
	int m=0,r=0;//知道m r就知道了l所以只需要记两个
	for(int i=1;i<=len2;i++)
	{
		if(i>r)
	  		p[i]=1;//此时回文区间不包含i那么就直接往外走  //m就为中间值嘛!!
		else p[i]=min(p[2*m-i],r-i+1); //此时比较大小 就是p的距离不能超过到左边的顶点 (这里等价到了右边因为是对称的
		while(i-p[i]>0&&i+p[i]<=len2&&t[i-p[i]]==t[i+p[i]])  //左边要大于0 右边要小于m 要存在  然后去由i暴力枚举回文子串
			++p[i];
		if(i+p[i]-1>r)//去更新!
			m=i,r=i+p[i]-1;
	}
	int ans=0;
	for(int i=1;i<=len2;i++)
		ans=max(ans,p[i]);// 如果中间是字母那么是以字符结束最后回文子串一定是ans-1
	//如果是标识符也满足  类似把左边的移到了右半边
	cout<<ans-1;   
        这里其实有两边左边也是右边也是因为是回文的f[i]只是记得是以i为中心最大的一个回文子串统计正确的原因 把左边的回文字符将右边的'$'字符替换!!所以这个做法是正确的!
        如果最边上的是合法字符那么 肯定还能再扩展一个因为是一个共同的非法字符  同理若是非法则统计的不就是要减掉去吗  故一定是正确的!!
}
int main()
{
	
}

这大概就是manacher算法的一个大体思路 和 exkmp 特别特别像!!!
当然回文子串常见的套路还有二分+哈希 有时候要用双哈希

#include <bits/stdc++.h>
using namespace std;
typedef int64_t i64;
int a[100005],b[200010];
int n,m;
i64 c[200010];
i64 h[200010],f[200010];//存储字符串哈希值   
const int base=1137,p=9999971;
i64 calc1(int l,int r)
{
	return (h[r]-h[l-1]*c[r-l+1]%p+p)%p;
}
i64 calc2(int l,int r)
{
	return (f[l]-f[r+1]*c[r-l+1]%p+p)%p;
}
int main()
{
	cin>>n;
	b[++m]=1001;//类似manacher原因避免奇偶性的讨论!!
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
		b[++m]=a[i];
		b[++m]=1001;
	}
	c[0]=1;
	for(int i=1;i<=m;i++)
	{
		c[i]=c[i-1]*base%p;
		h[i] =(h[i-1]*base%p+b[i])%p;
	//	cout<<b[i]<<' ';
	}
	for(int i=m;i;i--)
	{
		f[i]=(f[i+1]*base%p+b[i])%p;

	}
	int ans=0;//记录最大哈希值
	for(int i=1;i<=m;i++)
	{
		int l=0,r=min(i,m-i+1)+1;//枚举每一个位置 o(n) 然后用log 的复杂度计算到底最后会到哪里去!
		while(l+1<r)
		{
			int M=(l+r)/2;
			if(calc1(i,i+M-1)==calc2(i-M+1,i))
				l=M;
			else r=M;
		}
		ans=max(ans,l);
	}
	cout<<ans-1;
}


就要去维护一个这样的东西一个从前往后求值一个从后往前求一个哈希值这样才能保证这个做法是正确的!! 然后二分枚举的其实是区间的边界最多到哪个地方去!!
这就是二分+哈希

最小表示法

char s[2*N+2];
int n;
void getmin()
{
	n=strlen(s+1);
	for(int i=1;i<=n;i++)  //算法求:把一个字符串结成环求从任意一点断开后
						  //字符串的字典序最小
	{
		s[i+n]=s[i];
	}
	int i=1,j=2;  
	while(j<=n)
	{
		int k=0;
		while(k<n&&s[i+k]==s[j+k])
			++k;  //思路让i  j一直去比较  从i j本身值一个个往后去看  知道看到一个不相等的
		if(s[i+k]>s[j+k])
			i+=k+1;            
		else j+=k+1;        //然后让指针去移动
		if(i==j)
			++j;
		if(i>j)  
			swap(i,j);
	}  //判断完之后再重新计数  
	for(int k=i;k<=i+n+1;i++)
		cout<<s[i];
}

找到的肯定是一个字典序最小的:开头就是最小的 因为大的做开头会被更新掉,只有小的才会被留下来作为开头的一段

posted @ 2023-04-21 15:16  _Lance  阅读(19)  评论(0编辑  收藏  举报