POJ1159

这竟然是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;
}

猜你喜欢

转载自www.cnblogs.com/cjjsb/p/9029064.html