manacher算法用来求解回文串问题,时间复杂度为O(n).
不懂的先可以去练习下模板板子题,求最长回文串
传送门P3501 [POI2010]ANT-Antisymmetry
这一题他给的是一个新的定义“反对称”字符串:
如果将这个字符串0和1取反后,再将整个串反过来和原串一样,就称作“反对称”字符串。比如00001111和010101就是反对称的,1001就不是。
我们可以了利用0/1的性质,由于0/1始终不能与自己相反,故子串并不存在中央位置,我们只能从自己添加的特殊符号’#'的奇数位上找
,差不多就是manacher的一个模板题,就是循环时的判定条件发生了变化,回文串求的是str[i - Len[i]]== str[i + Len[i]],这里我们可以直接check来判定
bool check(char i, char j) {
if(((i - '0') ^ (j - '0')) == 1) return 1;//如果俩个数相反
if(i == '#' && j == '#') return 1;//如果特殊符号相等返回1
return 0;
}
不知名的代码
#include<bits/stdc++.h>
using namespace std;
const int N=2000010;
int p[N],n,m;
char s[N],str[N];
int len;
bool check(char i, char j) {
if(((i - '0') ^ (j - '0')) == 1) return 1;//如果俩个数相反
if(i == '#' && j == '#') return 1;//如果特殊符号相等返回1
return 0;
}
void manacher()
{
p[0]=0;
int mx=0;
int id=0;
int k=0;
str[k++]='%';
str[k++]='#';
for(int i=0;i<n;i++)
{
str[k++]=s[i];
str[k++]='#';
}
len=k;
for(int i=1;i<len;i+=2)//数字只会出现在偶数上,我们只要枚举所有的奇数位'#'来作为分割线
{
if(i<mx) p[i]=min(mx-i,p[2*id-i]);
else p[i]=1;
while(check(str[i-p[i]],str[i+p[i]])) p[i]++;//改变判定条件
if(p[i]+i>mx)
{
mx=p[i]+i;
id=i;
}
}long long ans=0;
// cout<<len<<endl;
// for(int i=1;i<=len-1;i++)
// cout<<str[i]<<" ";
// cout<<endl;
for(int i=1;i<=len-1;i+=2)
{
// cout<<p[i]<<" ";
if(p[i]>1) ans+=p[i]/2;
}
// if(p[i]>1) ans+=p[i]/2;
cout<<ans<<endl;
}
int main()
{
scanf("%d",&n);
scanf("%s",s);
manacher();
return 0;
}
不知道为什么p[i]/2的可以自己枚举一下
这一题又是一个整活题目,他要求的是最长双回文串
想到回文串我们就能利用manacher算法
该题目的双回文串就是:把一个字符串baacaabbacabb拆开
可以拆开为aacaa,bbacabb两个回文子串,求这个两个回文子串的最大长度是多少
我们可以利用’#'来当作切割的位置,求这个位置作为回文串的左端点的最大长度和作为右端点的最大长度,然后枚举一边加起来即可。
中间转移时我们可以利用dp
for(int i=1 ;i<=len-1 ;i+=2)
{
L[i] = max(L[i] , L[i-2] -2 );
}
这里转移是我们找最大长度,L[i-2]是我们只找’#‘上的位置,
L[i-2]-2是该位置L[i]在上一个位置的后面两位,回文串的长度也要 -2
转移过程
不知名代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e6+10;
int Len[N],r[N];
int len;
int R[N],L[N];
char s[N],str[N];
int n,m;
void manacher()
{
// memset(str,0,sizeof(str));
len=strlen(s);
int k=0;
str[k++] = '$';
for(int i=0;i<len;i++){
str[k++]='#';
str[k++]=s[i];
}
str[k++]='#';
len=k;
Len[0]=0;
int mx=0;
int id=0;
for(int i=1 ;i<len ;i++)
{
if( i < mx ) 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;
}
int r = i+Len[i]-1;
int l = i-Len[i]+1;
// printf("%d %d %d %d\n",i,r,l,Len[i]-1);
L[l] = max(Len[i]-1,L[l]);//以l为回文串的左端点的最长回文串
R[r] = max(Len[i]-1,R[r]);//以r为回文串的右端点的最长回文串
}
}
int main()
{
scanf("%s",s);
manacher();
int ans=0;
for(int i=1 ;i<=len-1 ;i+=2)
{
L[i] = max(L[i] , L[i-2] -2 );
}
//for(int i=1;i<=len;i+=2)
//cout<<L[i]<<" ";
for(int i=len-1 ;i>=1 ;i-=2) R[i] = max(R[i] , R[i+2] - 2 );
for(int i=1 ;i<=len-1;i+=2)
{if(R[i]&&L[i])
ans = max(ans , L[i] + R[i]);
}
cout<<ans<<endl;
return 0;
}
这题求的是前K大的回文串长度的乘积,也相当于一个板子题。
这题有个巨恶心的地方,最后一个点数据很大,要用快速幂qmi
来写,然后这题只要我们求回文串长度为奇数的回文串,最后枚举所有回文串长度时偶数直接可以跳过
不知名的代码
#include<bits/stdc++.h>
using namespace std;
const int N=3000010,mod=19930726;
typedef long long ll;
int Len[N];
char s[N],str[N];
ll cnt[N],sum,id,len;
ll n,m,K;
void manacher()
{
int k=0;
str[k++]='%';
for(int i=0;i<n;i++)
{
str[k++]='#';
str[k++]=s[i];
}
str[k++]='#';
len=k;
// cout<<len<<endl;
Len[0]=0;
int mx=0;
for(int i=1;i<len;i++)
{
if(i<mx) Len[i]=min(mx-i,Len[2*id-i]);
else Len[i]=1;
while(str[Len[i]+i]==str[i-Len[i]]) Len[i]++;
if(Len[i]+i>mx)
{
mx=Len[i]+i;
id=i;
}
if((Len[i]-1)%2) cnt[Len[i]-1]++;
}
// cout<<sum-1<<endl;
}
ll qmi(int x,int y)
{
if(x==1) return 1;
ll base=x,res=1;
while(y)
{
if(y&1) res=(res*base)%mod;
base=(base*base)%mod;
y>>=1;
}
return res;
}
void solve()
{
ll ans=1;
sum=0;
for(int i=n;i>=1;i--)
{
if(i%2==0) continue;
// printf("%d %d\n",i,cnt[i]);
sum+=cnt[i];
if(K>=sum)
{
ans*=qmi(i,sum)%mod;
K-=sum;
}
else
{
ans*=qmi(i,K)%mod;
K-=sum;break;
}
// cout<<ans<<endl;
}
if(K>0) cout<<-1<<endl;
else printf("%lld\n",ans);
}
int main()
{
scanf("%d%lld",&n,&K);
scanf("%s",s);
manacher();
solve();
return 0;
}