USACO2007 Open Cheappal

来源:互联网 发布:阿里云备案网站负责人 编辑:程序博客网 时间:2024/05/23 12:04

Description

追踪奶牛是一件棘手的任务。Kano安装了一套自动系统,为每头奶牛设置电子身份标签。当奶牛通过扫描器的时候,系统可以读取奶牛的身份信息。标签是由字符串组成的,长度为M,所用字符由N个小写字母组成。
有时候奶牛很顽皮,她们会倒着通过扫描器。这样一来,系统会把abcb读成bcba,导致识别出现障碍。但是abcba就不会发生这种问题,因为它是一个回文。
于是Kano想把所有的标签都改为回文:比如在abcb的最后添加a得到abcba;也可以在前面加上bcb得到bcbabcb;还可以去除字母a,只保留bcb。Kano可以在任意位置删除或插入字符。不巧的是,增加或删除字母都需要向代理商付费。
给定一头奶牛的身份标签,以及增加、删除相关字母的费用,找出把字符串变成回文的最小费用。注意的是空字符串也是一条回文。

Sample Input &Output

3 4
abcb
a 1000 1100
b 350 700
c 200 800

900


题意要求的是令字符串[1,m]成为回文的最小代价,在定义dp含义的时候我们可以根据上述题意,只考虑字符串中[i,j]成为回文的最小代价。

要注意的是,如果[i+1,j]已经是回文,那么删除掉str[i]与在[j,j+1]中间插入str[i]两个操作是等价的。所以对于题目中给的v1v2,只要取其中的较小值即可。

有两种dp的定义方式:

简单一点的定义是设dp[i][j]表示[i,j]成为回文的最小代价,我们用刷表法去更新在它外面的区间,并且也可以采用枚举回文长度的做法——将k=ji作为更新时的层。

转移:当str[i1]==str[j+1]时,dp[i1][j+1]=dp[i][j]

否则:

dp[i1][j]=min(dp[i1][j],dp[i][j]+cost[str[i]a])
dp[i][j+1]=min(dp[i][j+1],dp[i][j]+cost[str[j]a])

#include<cstdio>#include<cstring>#include<algorithm>#define M 2005#define inf (1<<30)using namespace std;char str[M];int dp[M][M],cost[M];void check(int &a,int b){if(a>b)a=b;}int main(){    int n,m;    scanf("%d %d %s",&n,&m,str+1);    for(int i=0;i<n;i++){        char buf[5];int v1,v2;        scanf("%s %d %d",buf,&v1,&v2);        cost[buf[0]-'a']=min(v1,v2);    }//删除一边的字母等价于添加另一边的字母     for(int i=1;i<=m;i++)        for(int j=1;j<=m;j++)            dp[i][j]=inf;    for(int i=1;i<=m;i++){        dp[i][i]=0;        if(str[i]==str[i+1])dp[i][i+1]=0;    }//考虑回文串长度为奇数和偶数的情况     for(int k=0;k<=m;k++)        for(int i=1;i+k<=m;i++){            int j=i+k,c1=str[i-1]-'a',c2=str[j+1]-'a';            if(c1==c2)check(dp[i-1][j+1],dp[i][j]);            check(dp[i-1][j],dp[i][j]+cost[c1]);            check(dp[i][j+1],dp[i][j]+cost[c2]);        }//有少量的下标溢出    printf("%d\n",dp[1][m]);    return 0;}

其实“增加多个字母变成回文”也就是一道经典题目——LCS的翻版。
显然是非常相似的:LCS也根据两端字母是否相同来进行更新。不同的是LCS是从能构成回文的情况得到个数,而这道题从不能构成回文的情况得到代价。

此时我们上面定义过的dp[i][j]可以有新的理解:从pos=(i+j)/2处剖开数组,Former数组中前posiLatter数组中前jpos已经完成LCS处理后的最小代价。

此时应该采用填表法更新,外层i逆序,内层j顺序(写到这里我才懵逼地发现并没有什么区别):

#include<cstdio>#include<cstring>#include<algorithm>#define M 2005#define inf (1<<30)using namespace std;char str[M];int dp[M][M],cost[30];void check(int &a,int b){if(a>b)a=b;}int main(){    int n,m;    scanf("%d %d",&n,&m);    scanf("%s",str+1);    for(int i=0,v1,v2;i<n;i++){        char buf[5];        scanf("%s %d %d",buf,&v1,&v2);        cost[buf[0]-'a']=min(v1,v2);    }    for(int i=m-1;i;i--)        for(int j=i+1;j<=m;j++){            int c1=str[i]-'a',c2=str[j]-'a';            if(c1==c2)dp[i][j]=dp[i+1][j-1];            else dp[i][j]=min(dp[i+1][j]+cost[c1],dp[i][j-1]+cost[c2]);        }    printf("%d\n",dp[1][m]);    return 0;}

然而这些都比较难想。
一般的dp都可以考虑从记忆化搜索入手,设dp[i][j]表示[1,i][j,m]两端已经匹配好回文的个数,那么在dfs的时候可以从中间往两边处理。

0 0