【算法学习笔记】70.回文序列 动态规划 SJTU OJ 1066 小M家的牛们

这个题很多地方暗示了DP的路径。

我们处理时,dp[i][j]可以认为是从i坐标到j坐标的序列达到回文效果需要的最小代价,以此向外扩展,最终得到dp[0][M-1]就是结果。

我们要注意到处理dp[i][j]时,我们需要知道 dp(i+1,j-1)的结果,所以i必须降序,j必须升序,才能保证在计算dp(i,j)时,可以利用已经计算过的结果。

所以 

i应该从M-2 到 0 递减

j在内层 从i+1到M-1 递增

在处理dp(i,j)时,第一要看name[i]和name[j]是否相等,如果相等的话,那么直接向内扣一层就可以了

dp[i][j] = dp[i+1][j-1]

否则我们要进行改造,使其成为回文字符串

这个改造有四种方法

1.先让i+1到j是回文的 再在最后加一个name[i]

2.先让i+1到j是回文的 再把前面的name[i]删掉 

3.先让i到j-1是回文的 再在最前面加一个name[j] 

4.先让i到j-1是回文的 再在最后的name[j]删掉 

选择一种消耗最小的方案就可以了。

代码如下

#include <iostream>
#include <cstring>

using namespace std;

struct chr
{
    char c;
    int add;
    int del;
};

chr v[30];
int N,M;
string name;

void init(){
    cin>>N>>M;
    //N是不同字母的数量
    //M是原始名字的长度
    cin>>name;
    //记录字母cost情况
    for (int i = 0; i < N; ++i)
    {
        char c;
        cin>>c;
        v[c-'a'].c = c;
        cin >>v[c-'a'].add >> v[c-'a'].del;
    }
}

unsigned long long dp[2500][2500]={0};//存储的是 从第i个字母 到 第j个字母 的回文最小消耗


int build(){
    //从后向前填满dp[i][j]
    for (int i = M-2; i >=0; --i){
        for (int j = i + 1 ; j < M; ++j){
            if(name[i]==name[j])
                dp[i][j] = dp[i+1][j-1];//如果i和j相同 就向内扣一个 不用任何操作
            else{
                //如果不相同 要考虑四种修改方法 
                dp[i][j] = 1<<30;
                unsigned long long cs[4];
                //注意 此时要保证 i+1,j 和 i,j-1都完成了 所以选择i降序 j升序
                cs[0] = dp[i+1][j] + v[name[i]-'a'].add; // 先让i+1到j是回文的 再在最后加一个i 
                cs[1] = dp[i+1][j] + v[name[i]-'a'].del; // 先让i+1到j是回文的 再把前面的i删掉 
                cs[2] = dp[i][j-1] + v[name[j]-'a'].add; // 先让i到j-1是回文的 再在最前面加一个j 
                cs[3] = dp[i][j-1] + v[name[j]-'a'].del; // 先让i到j-1是回文的 再在最后的j删掉
                
                for(int k = 0; k<4; ++k)
                    dp[i][j] = min(dp[i][j],cs[k]);
            }
        }
    }
    return dp[0][M-1];
}
int main(int argc, char const *argv[])
{
    init();
    cout<<build()<<endl;
    return 0;
}
原文地址:https://www.cnblogs.com/yuchenlin/p/sjtu_oj_1066.html