2018 BACS High School Programing Contest J.Non Super Boring Substring-manacher+雙指標
阿新 • • 發佈:2018-12-12
題意:
給出一個字串,求其子串中不包含長度大於等於k的迴文串的子串個數
思路:
對於單串迴文問題,考慮使用manacher演算法進行預處理,即可處理出以每個位置為中點的最長迴文半徑
使用雙指標技術,結合迴文半徑的資訊,處理出以每個位置為起點,在不構成大於等於k的迴文串前提下,向後能到達的最遠位置
對於任何一個大於k的迴文串,我們都可以將其看做長度為k或k+1的迴文串,因為只要包含長度大於等於k的迴文串就不合法,而任何一個長度大於k的迴文串都一定包含一個長度為k或k+1的迴文子串,所以只需要考慮最短的情況
由於manacher演算法會對原串進行擴充套件,導致每個字元的下標發生變化,處理時只需要/2即可還原成原位置的下標
對於一個長度為n的字串,其子串個數為n*(n+1)/2,對於我們處理出的每個區間,累加求和
因為兩個相鄰區間可能相互包含,對於被包含的部分還要容斥處理,減去多加的部分
程式碼:
#include<iostream>
#include<cstring>
#include <string>
#include<algorithm>
using namespace std;
const int maxl=2e5+5;
const int inf=0x3f3f3f3f;
int p[2*maxl+5]; //p[i]-1表示以i為中點的迴文串長度
int map[ maxl];
int k;
void Manacher(string s)
{
string now;
int len=s.size();
for(int i=0;i<len;i++) //將原串處理成%a%b%c%形式,保證長度為奇數
{
now+='%';
now+=s[i];
}
now+='%';
len=now.size();
int pos=0,R=0;
for (int i=0;i<len;i++)
{
if (i<R) p[i]=min(p[ 2*pos-i],R-i); else p[i]=1;
while (0<=i-p[i]&&i+p[i]<len&&now[i-p[i]]==now[i+p[i]]) p[i]++;
if (i+p[i]>R) {pos=i;R=i+p[i];}
}
for (int i=0;i<len;i++) //預處理區間資訊
{
if(p[i]-1<k) continue; //只考慮長度大於等於k的迴文串
int flag=(p[i]-1-k)%2; //判斷該串是包含長度為k的迴文子串還是長度為k+1的迴文子串
int ln;
if(flag)
ln=k+1;
else
ln=k;
int lx=(i-ln+1)/2; //左指標指向起點,下標還原成原串
int rx=(i+ln-1)/2; //右指標指最長可達位置,下標還原成原串
map[lx]=min(map[lx],rx); //一個起點可能被更新多次,只取最小的結果
}
}
string a;
int main()
{
std::ios::sync_with_stdio(false);
int t;
cin>>t;
while(t--)
{
cin>>k;
cin>>a;
memset(map,inf,sizeof(map));
Manacher(a);
int len=a.size();
int now=len;
for(int i=len;i>=0;i--) //求出以每個點為起點,在不包含大於等於k長度迴文子串前提,所能到達的最遠位置
{
if(map[i]!=inf)
now=map[i];
map[i]=now;
}
long long ans=0;
long long pre=0;
for(int i=0;i<len;i++)
{
long long tmp=map[i]-i;
ans+=(1+tmp)*tmp/2; //對於長度為tmp的字串,計算其子串個數
if(pre>=i) //相鄰兩區間存在交叉,容斥處理
{
tmp=pre-i;
ans-=(1+tmp)*tmp/2; //減去多加的部分
}
pre=map[i];
}
cout<<ans<<endl;
}
return 0;
}