1040 Longest Symmetric String
分数 25
作者 CHEN, Yue
单位 浙江大学
Given a string, you are supposed to output the length of the longest symmetric sub-string. For example, given Is PAT&TAP symmetric?
, the longest symmetric sub-string is s PAT&TAP s
, hence you must output 11
.
Input Specification:
Each input file contains one test case which gives a non-empty string of length no more than 1000.
Output Specification:
For each test case, simply print the maximum length in a line.
Sample Input:
Is PAT&TAP symmetric?
Sample Output:
11
1)动态规划:
/**
1)动态规划:
*/
/**
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int maxn=1010;
int dp[maxn][maxn];
int main()
{
int res=1;
string str;
getline(cin,str);
int len=str.size();
memset(*dp,-1,sizeof dp);
for(int i=0;i<len;++i)
{
dp[i][i]=1;
if(str[i]==str[i+1]&&i<len-1)
{
dp[i][i+1]=2;
res=2;
}
}
for(int l=3;l<=len;++l)
for(int i=0;i+l-1<len;++i)
{
int j=i+l-1;
if(str[i]==str[j]&&dp[i+1][j-1]!=-1)
{
dp[i][j]=dp[i+1][j-1]+2;
res=l;
}
}
cout << res << endl;
return 0;
}
*/
2)字符串hash求解
/**
求解字符串的字串的hash值
H[i]=H[i-1]*P+index(str[i])
H[i...j]=index(str[i])*P^(j-i)+index(str[i-1])*P^(j-i+1)+...+index(str[j])*P^0;
H[j]=H[j-1]*P+index(str[j])
=((H[j-2]*P+index(str[j-1]))*P)+index(str[j])
=......
=H[j-2]*P^2+index(str[j-1])*P+index(str[j])
=H[i-1]*P^(j-(i-1))+index(str[i])*P^(j-i)+index(str[i-1])*P^(j-i+1)+...+index(str[j])*P^0
=H[i-1]*P^(j-i+1)+H[i...j]
-> H[i...j]=H[j]-H[i-1]*P^(j-i+1)
SO 为了得到正数:
H[i...j]=(H[j]-H[i-1]*P^(j-i+1)%mod+mod)%mod;
*/
/**
最长回文子串:求出开始位置到第i位的子串的hash值;
然后就可以用求出的hash值进而求出子串的hash值;
同时,还需要用到该字符串的反转序列的hash值;
现在来分析一下原序列与反转序列的等价情况。
eg:
原序列: "abcbad"
反转序列: "dabcba"
"bad"在原序列的下标是[3,5];在反转序列中相同序列是[2,0],即[6-1-3,6-1-5];
所以原序列是str[i,j],则反转序列中相同序列是str[len-1-i,len-1-j](从后往前看)
那么反转序列中相反序列便是str[len-1-j,len-1-i]。
要求最长回文子串,便要考虑两种情况,分单数与双数。
单数:
对第i位求出最长回文半径k,使得str[i-k,i+k]是回文子串,也就是str[i-k,i]与str[i,i+k]
是相反子串,即str[i-k,i]与反转序列rstr[len-1-(i+k),len-1-i]是相同子串。
双数:
第i位表示回文左边最后一个元素的下标,求出最长回文半径k,使得str[i-k+1,i+k]是回文
子串,也就是str[i-k+1,i]与str[i+1,i+k]是相反子串,即str[i-k+1,i]与反转序列
rstr[len-1-(i+k),len-1-(i+1)]是相同子串。
利用二分法求最长回文半径,由于单数与双数的分析中:
单数:
str[i-k,i]与反转序列rstr[len-1-(i+k),len-1-i]是相同子串
双数:
str[i-k+1,i]与反转序列rstr[len-1-(i+k),len-1-(i+1)]是相同子串
单数与偶数的回文左边的第一个元素下标与偶数的回文右边的最后一个元素下标
相差一,而其他不变,因此可以合并为一个函数。只需要额外多加一个参数。
*/
/**
2)字符串hash:
str[i...j]的反转序列对应的下标是:rstr[len-1-i][len-1-j];
求最长回文半径,分单数和双数讨论;
奇数:以i为枚举中心,表示左序列的最后一个元素的下标,str[i-k,i+k]是回文子串,
等价于str[i-k,i]与str[i,i+k]是相反字串,即str[i-k,i]与rstr[len-1-(i+k),
len-1-i]是相同子串;
偶数:以i为枚举中心,表示左序列的最后一个元素的下标,str[i-k+1,i+k]是回文子串,
等价于str[i-k+1,i]与str[i+1,i+k]是相反字串,即str[i-k+1,i]与rstr[len-1-(i+k),
len-1-(i+1)]是相同子串;
*/
/**
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <string>
using namespace std;
const int maxn=1010;
typedef long long LL;
const LL P=1e7+19,mod=1e9+7;
LL H1[maxn],H2[maxn],powP[maxn];
void init(int len);
void hashfunc(string str,LL *H);
int hash_sub(LL *H,int l,int r);
int binary(int index,int l,int r,int len,int flag);
int main()
{
string str;
getline(cin,str);
int len=str.size();
init(len);
hashfunc(str,H1);
string rstr=str;
reverse(rstr.begin(),rstr.end());
hashfunc(rstr,H2);
int res=0;
/**
for(int i=0;i<len;++i) //奇数
{
int r=min(i,len-1-i)+1;
int temp=binary(i,0,r,len,0);
res=max(res,2*temp+1);
}
for(int i=0;i<len;++i) //偶数
{
int r=min(i+1,len-1-i)+1;
int temp=binary(i,0,r,len,1);
res=max(res,2*temp);
}
*/
for(int i=0;i<len;++i) //奇数
{
int r=min(i,len-1-i)+1;
int temp=binary(i,0,r,len,0);
res=max(res,2*temp+1);
}
for(int i=0;i<len;++i) //偶数
{
int r=min(i,len-1-i)+2;
int temp=binary(i,0,r,len,1);
res=max(res,2*temp);
}
cout << res << endl;
}
void init(int len)
{
powP[0]=1;
for(int i=1;i<len;++i)
powP[i]=(powP[i-1]*P)%mod;
}
void hashfunc(string str,LL *H)
{
H[0]=(str[0]-' '); //这儿可以直接用字符的ascii码,也可以减去' ' ,但是不能减去
for(int i=1;i<str.size();++i)//'a'或者其他字符,这样会造成不同子串的hash值相等;
H[i]=(H[i-1]*P+str[i]-' ')%mod;
}
int hash_sub(LL *H,int l,int r)
{
if(l==0)
return H[r]%mod;
else
return ((H[r]-H[l-1]*powP[r-l+1])%mod+mod)%mod;
}
int binary(int index,int l,int r,int len,int flag)
{
while(l<r)
{
int mid=(l+r)/2;
int L1=index-mid+flag,R1=index;
int L2=len-1-(index+mid),R2=len-1-(index+flag);
int hashL=hash_sub(H1,L1,R1);
int hashR=hash_sub(H2,L2,R2);
if(hashL!=hashR) //要求最大回文半径,即求最后一个hashL==hashR,也就是求第一个
r=mid; //hashL!=hashR的半径,再减去一便是答案;
else
l=mid+1;
}
return l-1;
}
3)如果要输出最长回文子串本身怎么办:把最长回文子串的起始下标存储下来便可。
代码:
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long LL;
void init_powP(int len);
void hashfunc(string str,LL *H);
int str_sub_hash(LL *H,int i,int j);
int binary_search_R(int l,int r,int len,int index,int is_even);
const LL P=1e7+19; //显而易见,由于int最大能表示的值为21亿,大概是1e9,而P*mod等于1e16,显然不能正确存储;
const LL mod=1e9+7; //long long型能存储1e19,远远足够。
const LL maxn=1001;
LL H1[maxn]={0},H2[maxn]={0},powP[maxn];
int main()
{
string str1;
getline(cin,str1);
int len=str1.size();
init_powP(len);
hashfunc(str1,H1);
string rstr=str1;
reverse(rstr.begin(),rstr.end());
hashfunc(rstr,H2);
int ans=0, max_index=0;
for(int i=0;i<len;++i)
{
int max_len=min(i,len-1-i)+1; //回文半径最多能取len/2,由于单数是以i为枚举点,左右两边
int k=binary_search_R(0,max_len,len,i,0);//最短长度作为回文半径,最后再加一,是因为
if(2*k+1>ans) //求回文半径的函数,第一个满足hashL!=hashR的回文半径,再减一,
{ //防止整个字符串都是回文串
ans=2*k+1;
max_index=i-k;
}
}
for(int i=0;i<len;++i)
{
int max_len=min(i+1,len-1-i)+1;//由于是偶数回文串,i是以回文串左边最右的下标作为枚举点,所以i要加1
int k=binary_search_R(0,max_len,len,i,1);
if(2*k>ans)
{
ans=2*k;
max_index=i-k+1;
}
}
cout << ans << endl;
cout << str1.substr(max_index,ans) << endl;
return 0;
}
void init_powP(int len)
{
powP[0]=1;
for(int i=1;i<=len;++i)
powP[i]=(powP[i-1]*P)%mod;
}
void hashfunc(string str,LL *H) //H[i]必然表示str的第一位到第i位的子串的hash值
{
H[0]=str[0]; //这儿可以直接用字符的ascii码,也可以减去' ' ,但是不能减去
for(size_t i=1;i<str.size();++i)//'a'或者其他字符,这样会造成不同子串的hash值相等;
H[i]=(H[i-1]*P+str[i])%mod;
}
//单数:
//str[i-k,i]与反转序列rstr[len-1-(i+k),len-1-i]是相同子串
//双数:
//str[i-k+1,i]与反转序列rstr[len-1-(i+k),len-1-(i+1)]是相同子串
int binary_search_R(int l,int r,int len,int index,int is_even)
{
int mid;
while(l<r)
{
mid=(l+r)/2;
int L1=index-mid+is_even,R1=index;
int L2=len-1-(index+mid),R2=len-1-(index+is_even);
int hashL=str_sub_hash(H1,L1,R1);
int hashR=str_sub_hash(H2,L2,R2);
if(hashL!=hashR) //要求最大回文半径,则转换为求最后一个满足hashL==hashR的回文半径,
r=mid; //即求第一个满足hashL!=hashR的回文半径,再减一,就得到最大回文半径
else
l=mid+1;
}
return l-1;
}
int str_sub_hash(LL *H,int i,int j)
{
if(i==0)
return H[j]%mod;
else
return ((H[j]-H[i-1]*powP[j-i+1])%mod+mod)%mod;
}
4)暴力枚举:
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxn=1001;
int longest(char* str,int left,int right);
int main()
{
char str[maxn];
printf("inupt a string:\n");
// gets(str);
fgets(str,maxn,stdin);
int len=strlen(str);
int count=1;
for(int i=0;i<len;i++)
{
int ans1=longest(str,i,i);
int ans2=longest(str,i,i+1);
int m=max(ans1,ans2);
count=max(m,count);
// count=max(count,max(ans1,ans2));
}
printf("%d\n",count);
return 0;
}
int longest(char *str,int left,int right)
{
int n=strlen(str);
while(str[left]==str[right]&&left>=0&&right<n)
{
left--;
right++;
}
return right-left-1;
}