演队在口试中非常不幸。在42道考题中,他恰好没有准备最后一道题,而刚好被问到的正是那道题。演队坐在教授面前,一句话也说不出来。但教授心情很好,给了演队最后一次通过考试的机会。他让这个可怜的学生说出考试要考的科目。不幸的是,演队想不起这个科目名字,尽管他记得科目里有诸如安全、程序、设备、可能还有信息学……
为了准备复试,演队决定记住这门课的名字。为了更好地记住那根长字符串,他决定把它分解成回文,并分别学习每个回文。当然,分解过程中的回文数必须尽可能少。
输入:
输入一行字符串表示为这门课的名字。这是由小写英文字母组成的非空行。这一行的长度最多是4000个字符。
输出:
第一行输出可以分解科目名称的最小的回文字符串数目。在第二行输出回文所有的回文字符串,由空格分隔。如果有几个答案是可能的,输出其中任何一个。
样例:
Input
pasoib
Output
6
p a s o i bInput
zzzqxxOutput
3
zzz q xx扫描二维码关注公众号,回复: 10651936 查看本文章Input
wasitacatisawOutput
1
wasitacatisaw
思路:
参考lmz的代码,感觉思路很好,枚举正哈希区间(i,j)和逆哈希区间和(n-j+1,n-i+1)区间中的回文序列,再进行递推记录下标,输出即可。
动态转移方程dp[i]=min(dp[j-1]+1,dp[i])
,dp[i]代表从1-i
区间的回文序列数量,通过正逆哈希可判断得当前区间中的回文序列数量,然后执行动态转移方程即可
参考代码:
#include<bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f3f
typedef unsigned long long ll;
const ll base=13331;
const ll maxn=1e5+5;
ll dp[maxn];//从1--i中回文串的最少个数
ll h1[maxn],h2[maxn],p[maxn],n;
ll pre[maxn];
char s[maxn];
string str[maxn];
ll get1(ll l,ll r)
{
return h1[r]-h1[l-1]*p[r-l+1];
}
ll get2(ll l,ll r)
{
return h2[r]-h2[l-1]*p[r-l+1];
}
bool check(ll i,ll j)
{
if(get1(i,j)==get2(n-j+1,n-i+1))
return true;
return false;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
memset(dp,inf,sizeof dp);
dp[0]=0;
cin>>(s+1);
n=strlen(s+1);
p[0]=1;
for(ll i=1; i<=n; i++)
{
h1[i]=h1[i-1]*base-s[i]-'a'+1;
h2[i]=h2[i-1]*base-s[n+1-i]-'a'+1;
p[i]=p[i-1]*base;
}
for(ll i=1; i<=n; i++)
{
for(ll j=1; j<=i; j++)
{
if(check(j,i))
{
if(dp[i]>(dp[j-1]+1))
{
pre[i]=j;
dp[i]=dp[j-1]+1;
}
}
}
}
cout<<dp[n]<<endl;
int id=0;
int now=n;
while(now)
{
//cout<<now<<endl;
for(int i=pre[now]; i<=now; i++)
str[id]+=s[i];
now=pre[now]-1;
id++;
}
for(int i=id-1; i>=0; i--)
{
if(i!=0)
cout<<str[i]<<' ';
else
cout<<str[i]<<endl;
}
}