P3805 【模板】manacher 算法https://www.luogu.com.cn/problem/P3805,就是求最长回文子串,呃这么说呢,重点:
第一点就是,呃回文中心,可能不在字符串上,那怎么办呢,你可以尝试插入一些原来不在串中的字符,比如说‘#’,就在每两个字符之间插入,就可以保证就算中心不在串上也没有问题就像这样:~|A|B|C|C|B|A|,前面的那个是什么捏,就是边界啦(防止越界)。
第二点( >,< ),就是呃那个维护盒子,(https://blog.csdn.net/qq_43456058/article/details/94588721)与(https://www.zhihu.com/question/37289584)都看起来不错(虽然没认真看),呃其实就是维护r与mid,如果当前节点的回文超出有右边界那就暴力拓展,不然就通过回文性分两种情况讨论即可。(luogu的题解我看的是这个哦)。
注意 p [ i ] 构成的最长度包括了它自己所以后面要减一
代码呀!说起来这个的时间是线性的为什么捏因为O(n)呀(大雾)不想给证明。
#include<bits/stdc++.h>
using namespace std;
int n,m;
int tot=0;
char a[11000002],s[11000002*2];
int p[11000002*2];
int ans=0;
int main()
{
scanf("%s",a+1);
s[0]='&';s[++tot]='|';
for(int i=1;a[i];i++) s[++tot]=a[i],s[++tot]='|';//不要每次都算strlen直接用这个就好
for(int r=0,mid=0,i=1;i<=tot;i++)
{
if(i<=r) p[i]=min(p[mid*2-i],r-i+1);//这一句可以手推一下中点公式即可
while(s[i-p[i]]==s[i+p[i]]) p[i]++;//如果能拓展的话,注意p [ i ]会先拓展自己所以不会出现 p[i] = 0的情况
if(p[i]+i>r) r=p[i]+i-1,mid=i;//如果超出边界
if(p[i]>ans) ans=p[i];
}
printf("%d",ans-1);
return 0;
}
接下来第一题是这个:P4555 [国家集训队]最长双回文串,呃确实看起来奇奇怪怪的题目,但是只要用递推式就变得离谱了呢,就保存 ll 以i为左端点的最长的回文串,rr 以i为右端点的最长的回文串,so就这样,对模板做了一点优化,不再是最后减一,而是在过程中减去了。题解:https://www.luogu.com.cn/problem/solution/P4555
代码:
#include<bits/stdc++.h>
using namespace std;
int n,m;
int net[100001];
int len,len2;
char a[100001],s[220001];
int f[1000001],p[100001];
int ll[110001],rr[100001];//ll指的是 以i开头的最长串,rr指的是以i结尾的最长串
//因为字符串不能重叠,所以我们只能枚举“ | ”
int main()
{
scanf("%s",a+1);
int tot=strlen(a+1);
s[0]='~',s[++len]='|';
for(int i=1;i<=tot;i++) s[++len]=a[i],s[++len]='|';
for(int i=1,mid=0,r=0;i<=len;i++)
{
if(i<=r) p[i]=min(p[mid*2-i],r-i+1);
while(s[i-p[i]]==s[i+p[i]]) p[i]++;
if(r<p[i]+i) mid=i,r=i+p[i]-1;
ll[i-p[i]+1]=max(ll[i-p[i]+1],p[i]-1);//这就是推啦
rr[i+p[i]-1]=max(rr[i+p[i]-1],p[i]-1);//嗯嗯
}
for(int i=2;i<=len;i+=2) ll[i]=max(ll[i],ll[i-2]-2);//就是回文串中间其实是没有算的所以要算一下
for(int i=len;i>=2;i-=2) rr[i]=max(rr[i],rr[i+2]-2);//嗯嗯
int ans=0;
for(int i=2;i<=len;i++)
{
if(ll[i]&&rr[i]) ans=max(ans,ll[i]+rr[i]);//如果可行不为0
}
printf("%d",ans);
return 0;
}
下一题冲!
P1659 [国家集训队]拉拉队排练,确实离谱啊,这一题让我想起来多年未见的快速幂模板,实则不难,但处理方式还是要学一学,总结一下就是统计之前用过的,新的就用快速幂处理,然后分两种情况:
if(sum>k)
以及
if(sum<=k)
这两种就好了,代码如下:
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m,k;
char s[2000001];
int cnt[2000001],p[2000001];
int ksm(int a,int k)
{
if(a==1) return 1;
int rt=1;
while(k>0)
{
if(k%2!=0) rt=(rt*a)%19930726;
a*=a;a%=19930726;
k>>=1;
}
return rt;
}
signed main()
{
scanf("%lld%lld",&n,&k);
scanf("%s",s+1);
s[0]='~';//因为这题只要奇数个所以不用做隔板
for(int i=1,r=0,mid=0;i<=n;i++)
{
if(i<=r) p[i]=min(p[mid*2-i],r-i+1);
while(s[i+p[i]]==s[i-p[i]]) p[i]++;
if(p[i]+i>r) mid=i,r=p[i]+i-1;
cnt[p[i]*2-1]++;
}
int sum=0,ans=1;
if(n%2==0) n--;
for(int i=n;i>=1;i-=2)
{
sum+=cnt[i];
if(sum>k)
{
ans*=ksm(i,k);
ans%=19930726;
break;
}
else
{
ans*=ksm(i,sum);
ans%=19930726;
k-=sum;
}
}
if(sum<k) printf("-1");
else printf("%lld",ans);
}
冲!P5446 [THUPC2018]绿绿和串串,一道结论紫题,还好吧,打这题的时候太晚了就没写什么直接看题解吧:https://www.luogu.com.cn/problem/solution/P5446
代码:
#include<bits/stdc++.h>
using namespace std;
int n,m;
int v[1000005*2],p[1000005*2];
char s[1000005*2],a[1000005*2],s1[1000005*2];
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
int len=0;
memset(v,0,sizeof(v));
memset(p,0,sizeof(p));
scanf("%s",a+1);
n=strlen(a+1);
for(int i=1;i<=1000005*2;i++) s[i]=s1[i];//之前忘记清空所以好像没拿到分问题不大
s[0]='~',s[++len]='|';
for(int i=1;i<=n;i++) s[++len]=a[i],s[++len]='|';
for(int i=1,r=0,mid=0;i<=len;i++)
{
if(i<=r) p[i]=min(p[mid*2-i],r-i+1);
while(s[i-p[i]]==s[i+p[i]]) p[i]++;
if(i+p[i]>r) r=i+p[i]-1,mid=i;
}
for(int i=len;i>=1;i--)
{
if(i+p[i]-1==len) v[i]=1;
else if(v[i+p[i]-2]&&i==p[i]) v[i]=1;//如果它能翻转成某个最长翻转子串,如 qwqwq 的 qwqw 的 qw ,且不会越界
}
for(int i=1;i<=len;i++)
{
if(s[i]>='a'&&s[i]<='z'&&v[i]) printf("%d ",i/2);
}
printf("\n");
}
return 0;
}
最后一题!
P6216 回文匹配难的要死,明天早上写(现在补),可恶,消息好坏参半,呃呃呃就说这道题不简单,比上一道难很多啊,看起来是kmp+马拉车,做起来才发现,要来一个二次前缀和,(https://www.luogu.com.cn/problem/solution/P6216)挂一下luogu的题解 ,开始想的是n*n的算法直接用kmp算出匹配串然后再用manacher枚举区间然后就可以安稳的暴毙,想优化,那怎么办呢,于是我们推一下公式,直接挂别人的吧:所以
所以就这样子啦,不得不说这个二次前缀和真的很灵性。
注意代码来啦!
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m;
char s[3001001],t[3001001];
int len1,len2;
int net[3001001],p[3001001];
int sum[3001001];
signed main()
{
scanf("%lld%lld",&n,&m);
scanf("%s%s",s+1,t+1);
net[0]=net[1]=0;
len1=strlen(s+1);len2=strlen(t+1);
s[0]='~';t[len2+1]='~';
for(int i=2,j=0;i<=len2;i++)
{
while(j&&t[i]!=t[j+1]) j=net[j];
if(t[i]==t[j+1]) j++;
net[i]=j;
}
for(int i=1,j=0;i<=len1;i++)
{
while(j&&s[i]!=t[j+1]) j=net[j];
if(s[i]==t[j+1]) j++;
if(j==len2)
{
sum[i-len2+1]++;
j=net[j];
}
}
for(int i=1;i<=len1;i++) sum[i]+=sum[i-1];
for(int i=1;i<=len1;i++) sum[i]+=sum[i-1];
for(int i=1,r=0,mid=0;i<=len1;i++)
{
if(i<=r) p[i]=min(p[mid*2-i],r-i+1);
while(s[i+p[i]]==s[i-p[i]]) p[i]++;
if(p[i]+i>r) mid=i,r=i+p[i]-1;
}
int ans=0;
for(int i=1;i<=len1;i++)
{
if(2*p[i]-1<len2) continue ;
int l=i-p[i]+1,r=i+p[i]-1;
l--;r=r-len2+1;
int mid=(l+r)/2;
ans+=(sum[r]-sum[mid]-sum[((l+r)&1)?mid:mid-1]+sum[l-1]);//注意这句是抄别人的主要是判断奇偶性
ans%=4294967296;
}
printf("%lld",ans);
return 0;
}