2 abc abaadada
Yes No
附上该题对应的中文题
判断是否能将字符串S分成三段非空回文串。
第一行一个整数T,表示数据组数。T≤20 对于每一个组,仅包含一个由小写字母组成的串。1≤∣S∣≤20000
对于每一组,单行输出"Yes" 或 "No"。
2 abc abaadada
Yes No
出题人的解题思路:
对原串前缀和后缀作一个01标记pre[i],suf[i]表示1-i和i-n能否能形成回文。记以i为中心的回文半径为r(i)。
这些都可以在O(N)时间内求出。也可以使用Hash+二分等方法O(NlogN)内求出。
我们考虑中间一个回文串的位置,不妨设它是奇数长度(偶数类似)。
那么问题变成了求一个i和d使得1<=d<=r(i)且pre[i-d]和suf[i+d]为真。
枚举i,实际上就是问pre[i-r(i)..i-1]和suf[i+1..i+r(i)]取反后 这两段有没有一个位置两者均为1,也就是and后不为0,暴力压位即可。
总时间复杂度为O(N2/32)。
说实话,出题人给的题解思路真心有点令人费解,或许是因为我还是一名菜鸟的缘故吧。前段时间刚好了解到了Manacher算法(一种计算最长回文子串的时间复杂度为O(n)的算法,想要学习一下的点链接吧Manacher算法),于是就拉了一下模板。
首先呢,Manacher算法中有一个p数组,可以记录回文子串在原串中的长度,然后枚举三个非空回文子串中的第一个和第三个(因为这两个比较特殊,毕竟接地气嘛,因为第一个是原字符串的开始,第三个是原字符串的结束),接着判断中间的回文串是否能接触到两边的回文串即可
/* */ #include<stdio.h> #include<string.h> #include<stdlib.h> #include<queue> #include<math.h> #include<vector> #include<map> #include<set> #include<string> #include<algorithm> #include<iostream> #define exp 1e-10 using namespace std; const int N=20005; char s[N*2]; int p[N*2],top[N*2],tail[N*2]; void Manacher() { int len = strlen(s), id = 0, maxlen = 0; for (int i = len;i >= 0;--i) { s[i + i + 2] = s[i]; s[i + i + 1] = '#'; } s[0] = '*'; for (int i = 2;i < 2 * len + 1;++i) { if (p[id] + id > i)p[i] = min(p[2 * id - i], p[id] + id - i); else p[i] = 1; while (s[i - p[i]] == s[i + p[i]])++p[i]; if (id + p[id] < i + p[i])id = i; if (maxlen < p[i])maxlen = p[i]; } //cout << maxlen - 1 << endl; } int main() { int t,i,j,k,k1,k2,r1,r2,mid; bool flag; scanf("%d",&t); while(t--) { k1=k2=0;flag=false; scanf("%s",s); Manacher(); k=strlen(s); for(i=2;i<k-1;i++) { if(i-p[i]==0) top[k1++]=i; if(i+p[i]==k) tail[k2++]=i; } for(i=0;i<k1;i++) { for(j=k2-1;j>=0;j--) { r1=top[i]+p[top[i]]; r2=tail[j]-p[tail[j]]; if(r1>r2) break; mid=(r1+r2)/2; if(p[mid]-1>=mid-r1) flag=true; if(flag) break; } if(flag) break; } if(flag) printf("Yes\n"); else printf("No\n"); } return 0; }