首页 > 其他分享 >【题解】【切开字符串】

【题解】【切开字符串】

时间:2022-11-10 15:23:08浏览次数:69  
标签:子串 ch int 题解 本质 后缀 切开 字符串 回文

P8631 [蓝桥杯 2015 国 AC] 切开字符串

Sol

首先问题可以转化为对每个前缀求出本质不同奇回文子串数,和对每个后缀求出本质不同子串数和本质不同奇回文子串数。

本质不同子串数

对每个后缀求出本质不同子串数,考虑后缀数组。对于从位置\(i\)开始的后缀,这个后缀的本质不同子串数即为把\(i,i+1,\cdots ,n\)后缀排序后,两两相邻的\(LCP\),于是可以从后往前把每个后缀不断加进去,每加入一个后缀,就把跟他相邻的后缀的贡献更新,因为每次需要查区间\(min\),所以要用\(st\)表维护。但是换个枚举顺序,考虑从\(1\)开始,不断从集合中删去\(1,2,3,\cdots\)的后缀,因为两点之间的\(min\)的范围不断变大,只需用链表就可以线性维护。

本质不同奇回文子串数

对于本质不同奇回文子串数,首先有个结论:长度为\(n\)的字符串最多有\(n\)个本质不同的回文子串。

证明:

考虑把每个本质不同的回文子串记录在其第一次出现的位置的右端点上。
假设右端点\(r\)上,存在两个长度不同的回文子串,长度分别为\(p,q\),不妨设\(p<q\),那么由于两个串都回文,可以得到长度为\(p\)的串在\(i-q+p\)就出现过,与前提矛盾,因此每个右端点最多记录一个回文子串。

求法:

通过证明可以看出每个位置记录的回文子串一定是最长的那个,先通过manacher对每个回文中心\(x\)求出其回文半径\(p_x\),然后对每个\((x,p_x)\),令\([x,x+p_x-1]\)对x取min,最后每个位置得到的即为最长的奇回文子串的回文中心,把每个右端点对应的回文子串hash一下,就可以统计每个前缀中本质不同的奇回文子串数。区间取min发现可以拓展到一个前缀取min,因此,也可以做到线性。

总结:

如果求后缀数组使用线性做法的话,整道题的复杂度即为\(O(n)\),不过笔者比较菜,这里用的倍增写法。

Code

点击查看代码
#include<bits/stdc++.h>
using namespace std;
//#define int long long
#define fi first
#define se second
#define pb push_back
int rd()
{
	int x=0,w=1;
	char ch=getchar();
	while(!isdigit(ch)&&ch!='-') ch=getchar();
	if(ch=='-') w=-1,ch=getchar();
	while(isdigit(ch)) x=x*10+ch-'0',ch=getchar();
	return x*w;
}
const int N=2e5+100;
int n,sa[N],rk[N],h[N],y[N],t[N],pre[N],suf[N],p[N],f[2][N];
char s[N];
long long w[N];
const int P=37;
unsigned long long hs[N],pw[N];
int val[N];
unordered_map<unsigned long long ,int>mp[N];
int geths(int l,int r)
{
	return hs[r]-hs[l-1]*pw[r-l+1];
}
void manacher()
{
	p[1]=1;
	int id=1,mx=2;
	for(int i=2;i<=n;++i)
	{
		p[i]=min(mx-i,p[(id<<1)-i]);
		while(s[i+p[i]]==s[i-p[i]]) p[i]++;
		if(i+p[i]>mx) id=i,mx=i+p[i];
	}
}
void calc(int op)
{
	for(int i=1;i<=n;++i) hs[i]=hs[i-1]*P+s[i]-'a'+1;
	manacher();
	for(int i=1;i<=n;++i) val[i]=n;
	for(int i=1;i<=n;++i) val[i+p[i]-1]=min(val[i+p[i]-1],i);
	for(int i=n-1;i;--i) val[i]=min(val[i],val[i+1]);
	for(int i=1;i<=n;++i)
	{
		f[op][i]=f[op][i-1]+(!mp[i-val[i]][geths(val[i],i)]);
		mp[i-val[i]][geths(val[i],i)]=1;
	}
	for(int i=1;i<=n;++i) mp[i-val[i]].clear();
}
int m=256;
void getsa()
{
	for(int i=1;i<=n;++i) t[rk[i]=s[i]]++;
	for(int i=1;i<=m;++i) t[i]+=t[i-1];
	for(int i=1;i<=n;++i) sa[t[rk[i]]--]=i;
	for(int k=1;k<n;k<<=1)
	{
		int cnt=0;
		for(int i=n-k+1;i<=n;++i) y[++cnt]=i;
		for(int i=1;i<=n;++i) if(sa[i]>k) y[++cnt]=sa[i]-k;
		for(int i=1;i<=m;++i) t[i]=0;
		for(int i=1;i<=n;++i) t[rk[i]]++;
		for(int i=1;i<=m;++i) t[i]+=t[i-1];
		for(int i=n;i;--i) sa[t[rk[y[i]]]--]=y[i];
		for(int i=1;i<=n;++i) y[i]=rk[i];
		m=0;
		for(int i=1;i<=n;++i) rk[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?m:++m;
		if(m==n) break;
	}
	for(int i=1,j,k=0;i<=n;h[rk[i]]=k,++i)
	for(k?--k:0,j=sa[rk[i]-1];s[j+k]==s[i+k];++k);
}
signed main()
{
    n=rd();pw[0]=1;
    for(int i=1;i<=n;++i) pw[i]=pw[i-1]*P;
    scanf("%s",s+1);
    getsa();
    w[1]=1ll*n*(n+1)/2;
    for(int i=1;i<=n;++i) w[1]-=h[i];
    for(int i=1;i<=n;++i) pre[i]=i-1,suf[i]=i+1;
    suf[n]=0;
    for(int i=1;i<n;++i)
    {
    	w[i+1]=w[i]-(n-i+1)+h[rk[i]];
    	if(suf[rk[i]]){
    		w[i+1]+=h[suf[rk[i]]];
    		h[suf[rk[i]]]=min(h[suf[rk[i]]],h[rk[i]]);
    		w[i+1]-=h[suf[rk[i]]];
    		pre[suf[rk[i]]]=pre[rk[i]];
		}
		if(pre[rk[i]]) suf[pre[rk[i]]]=suf[rk[i]];
	}
	calc(0);
	reverse(s+1,s+n+1);
	calc(1);
	reverse(f[1]+1,f[1]+n+1);
	long long ans=0;
	for(int i=1;i<n;++i)
	{
		ans=max(ans,f[0][i]*(w[i+1]-f[1][i+1]));
	}
	cout<<ans;
	return 0;
}

标签:子串,ch,int,题解,本质,后缀,切开,字符串,回文
From: https://www.cnblogs.com/glq-Blog/p/16877142.html

相关文章