[Jsoi2016]扭動的回文串
Description
JYY有兩個長度均為N的字符串A和B。
一個“扭動字符串S(i,j,k)由A中的第i個字符到第j個字符組成的子串
與B中的第j個字符到第k個字符組成的子串拼接而成。
比如,若A=’XYZ’,B=’UVW’,則扭動字符串S(1,2,3)=’XYVW’。
JYY定義一個“扭動的回文串”為如下情況中的一個:
1.A中的一個回文串;
2.B中的一個回文串;
3.或者某一個回文的扭動字符串S(i,j,k)
現在JYY希望找出最長的扭動回文串。
Input
第一行包含一個正整數N。
第二行包含一個長度為N的由大寫字母組成的字符串A。
第三行包含一個長度為N的由大寫字母組成的字符串B。
1≤N≤10^5
Output
輸出的第一行一個整數,表示最長的扭動回文串。
Sample Input
5
ABCDE
BAECB
Sample Output
5
HINT
最佳方案中的扭動回文串如下所示(不在回文串中的字符用.表示):
.BC..
..ECB
首先我們需要知道扭動的回文串的兩種情況
1、它為A串或B串的子串
2、它的對稱中心有一部分在A串或B串
對於第一種情況十分好寫,這裏就不再多說,主要是講講第二種情況
對於 第二種情況而言,我們首先枚舉回文串中點 i,然後找到最大能擴張的最大範圍(i-p[i]~i+p[i]),若回文串的中點在A串,則A串所能繼續取到的範圍是(1~i-p[i]-1),而B串中所能取到的範圍是(i+p[i]~len),若中心點在B串類似。
那麽下一步我們該怎麽做?二分長度。二分一個長度,然後判斷A的一部分和B串的一部分是否一樣
如何判斷?哈希。記錄哈希出來的值的前綴和,B串記錄後綴和,判斷的時候做類似前綴和的減法即可,記得雙哈希
依然不懂?上代碼
#include<cmath> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define inf 0x7f7f7f7f using namespace std; typedef long long ll; typedef unsigned int ui; typedef unsigned long long ull; inline int read(){ int x=0,f=1;char ch=getchar(); for (;ch<‘0‘||ch>‘9‘;ch=getchar()) if (ch==‘-‘) f=-1; for (;ch>=‘0‘&&ch<=‘9‘;ch=getchar()) x=(x<<1)+(x<<3)+ch-‘0‘; return x*f; } inline void print(int x){ if (x>=10) print(x/10); putchar(x%10+‘0‘); } const int N=1e5,limit=27,p1=100007,p2=233333; char A[N*2+10],B[N*2+10]; int pA[N*2+10],pB[N*2+10]; int sumA[2][N*2+10],sumB[2][N*2+10],g[2][N*2+10]; int len,Ans; void work(char *s,int *p){ int Max=0,ID=0; for (int i=1;i<=len;i++){ p[i]=Max>i?min(p[ID*2-i],Max-i):1; while (s[i+p[i]]==s[i-p[i]]) p[i]++; if (Max<p[i]+i) Max=p[ID=i]+i; } } bool check(int l1,int r1,int l2,int r2,int Len){ //哈希判斷,利用前綴和 int x=(sumA[0][r1]-1ll*sumA[0][l1-1]*g[0][Len]%p1)%p1; int y=(sumB[0][l2]-1ll*sumB[0][r2+1]*g[0][Len]%p1)%p1; x=(x+p1)%p1,y=(y+p1)%p1; if (x!=y) return 0; x=(sumA[1][r1]-1ll*sumA[1][l1-1]*g[1][Len]%p2)%p2; y=(sumB[1][l2]-1ll*sumB[1][r2+1]*g[1][Len]%p2)%p2; x=(x+p2)%p2,y=(y+p2)%p2; return x==y; } int solve(int j,int k){ //二分枚舉長度 int l=0,r=min(j,(len>>1)-k+1),ans=0; while (l<=r){ int mid=(l+r)>>1; if (check(j-mid+1,j,k,k+mid-1,mid)) l=mid+1,ans=mid; else r=mid-1; } return ans; } int main(){ len=read(); scanf("%s%s",A+1,B+1); for (int i=len;i;i--) A[i<<1]=A[i],B[i<<1]=B[i],A[i<<1|1]=B[i<<1|1]=‘&‘; len=len<<1|1; A[0]=B[0]=‘#‘,A[1]=B[1]=‘&‘,A[len+1]=B[len+1]=‘^‘,g[0][0]=g[1][0]=1; work(A,pA),work(B,pB); for (int i=1;i<=len;i++) pA[i]--,pB[i]--; //回文串的長度會多出來一位,應該減去 for (int i=1;i<=len;i++) Ans=max(Ans,max(pA[i],pB[i])), //回文串為子串的情況 g[0][i]=1ll*g[0][i-1]*limit%p1, g[1][i]=1ll*g[1][i-1]*limit%p2; //記錄類似進制一樣的東西 for (int i=2;i<len;i+=2) sumA[0][i>>1]=(1ll*sumA[0][(i>>1)-1]*limit+A[i])%p1, sumA[1][i>>1]=(1ll*sumA[1][(i>>1)-1]*limit+A[i])%p2; //記錄兩個哈希的前綴和,每次多出一位要乘上一個進制(limit) for (int i=len-1;i>1;i-=2) sumB[0][i>>1]=(1ll*sumB[0][(i>>1)+1]*limit+B[i])%p1, sumB[1][i>>1]=(1ll*sumB[1][(i>>1)+1]*limit+B[i])%p2; //由於對稱,所以B串的記錄要從後面開始 for (int i=2;i<len;i++){ //回文串中心在A串中的情況 int l=i-pA[i],r=i+pA[i]; l=(l+1)>>1,r>>=1; Ans=max(Ans,pA[i]+solve(l-1,r)*2); } for (int i=2;i<len;i++){ //回文串中心在B串中的情況 int l=i-pB[i],r=i+pB[i]; l=(l+1)>>1,r>>=1; Ans=max(Ans,pB[i]+solve(l,r+1)*2); } printf("%d\n",Ans); return 0; }
[Jsoi2016]扭動的回文串