这竟然是IOI虽然是2000年的,但其实也改变不了它水题的本质
我写了两种方法,这里都讲一下吧
考虑记忆化搜索,用f[i][j]表示当区间的左端为i,右端为j时最少要添加多少字符,所以ans就为f[1][n]
然后考虑一下,对于每一个f[i][j],都有转移:
-
s[i]==s[j],则有f[i][j]=f[i+1][j-1]
-
s[i]!=s[j] ,则有f[i][j]=min(f[i+1][j],f[i][j-1])(左右两边加一个字符,看看那种情况更优)
这里连枚举的顺序也懒得推了,直接跑了个记忆化搜索就过了
CODE
#include<cstdio>
#include<cstring>
using namespace std;
const int N=5005;
short int f[N][N],n;
char s[N];
inline int min(int a,int b)
{
return a<b?a:b;
}
inline void DP(int l,int r)
{
if (l>=r) { f[l][r]=0; return; }
if (s[l]==s[r])
{
if (f[l+1][r-1]==-1) DP(l+1,r-1);
f[l][r]=f[l+1][r-1];
} else
{
if (f[l+1][r]==-1) DP(l+1,r);
if (f[l][r-1]==-1) DP(l,r-1);
f[l][r]=min(f[l+1][r],f[l][r-1])+1;
}
}
int main()
{
scanf("%d%s",&n,s+1);
memset(f,-1,sizeof(f));
DP(1,n);
printf("%d",f[1][n]);
return 0;
}
注意这里的内存问题,开int的话都是要MLE的,但是由于数据范围5000,因此开short int足矣
还有一种算法,就是很套路的了
我们很轻易的发现,将原串倒过来之后,他们的最长公共子序列LCS都是不用再添加字符的,而对于其它的字符每个都要找一个字符与之相对应地匹配
证明不难,这里省略了,观察即可得出
LCS的DP方程也很简单,用f[i][j]表示第一个串前i个字符,第二个串前j个字符的LCS是多少,转移有
-
s[i]==s[j] f[i][j]=f[i-1][j-1]+1
-
s[i]!=s[j] f[i][j]=max(f[i-1][j],f[i][j-1])(之前的决策二选一)
是不是觉得和第一种的DP式有几分相似?其实他们本质上也是一样的!
所以又可以请出滚存来优化内存了
CODE
#include<iostream>
#include<string>
using namespace std;
const int N=5005;
int f[2][N],n;
string s1;
inline int max(int a,int b)
{
return a>b?a:b;
}
int main()
{
int i,j;
ios::sync_with_stdio(false);
cin>>n>>s1; string s2(s1.rbegin(),s1.rend());
for (i=0;i<n;++i)
{
int now=(i+1)&1,lst=now^1;
for (j=0;j<n;++j)
if (s1[i]==s2[j]) f[now][j+1]=f[lst][j]+1; else f[now][j+1]=max(f[lst][j+1],f[now][j]);
}
cout<<n-f[n&1][n]<<endl;
return 0;
}