1. 程式人生 > >2018 BACS High School Programing Contest J.Non Super Boring Substring-manacher+雙指標

2018 BACS High School Programing Contest J.Non Super Boring Substring-manacher+雙指標

在這裡插入圖片描述 在這裡插入圖片描述

題意:

給出一個字串,求其子串中不包含長度大於等於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; }