题目
动态规划作业题3-17
对于长度相同的两个字符串A和B,距离定义为相应位置字符距离之和.两个非空格字符的距离是它们的ASCII码之差的绝对值.空格和空格的距离为0,空格与其他字符的距离为一定值k.
字符串的扩展是指在原字符串中插入若干空格字符所产生的字符串.给定两个字符串A和B,A和B的所有长度相同的扩展中的距离最小的扩展的距离称为扩展距离,求A和B的扩展距离.
输入包括两个字符串A和B,一个整数k.
输出一个数字,A和B的最小扩展距离.
分析
刚看到这道题时懵逼了好一会,不知道它到底是在干什么.然后想到看看能不能把它转化为已有的知识.
首先想一下如果某个字符串中本来就有空格怎么办?
实际上,总能通过向另外一个字符串相应位置加空格来抵消,这个过程花费为0.
然后想一下A字符串只有一个字符的情况,它需要匹配一个B字符串中的字符,或者和空格匹配,除此之外,还有(|B|-1)*k的额外代价.如果B字符串中存在一个和A最接近且距离小于2k的,就匹配,否则全部与空格匹配.
A字符串有多个字符呢?想到这里就会发现,一个平凡的情况是全与空格匹配,代价(|A|+|B|)*k,我们需要通过确定一些匹配来使得这个值更小.
再仔细想一下,’空格’这个概念实际上是无关紧要的,一个字符可以选择和对面字符串的某个字符匹配,或者不匹配,代价直接+k就可以.
然后就是dp三连.
状态表示
dp[i][j]表示A[1]到A[i]与B[1]到B[j]的扩展距离,假定这个值已经是最优.
状态转移
如果dp[i][j]包括A[i]与B[j]匹配的情况,那么显然dp[i][j]=dp[i-1][j-1]+|A[i]-B[j]|
如果dp[i][j]中A[i]不会与B[j]匹配,那么dp[i][j]=dp[i][j-1]+k
同理如果dp[i][j]中B[j]不会与A[i]匹配,那么dp[i][j]=dp[i-1][j]+k
可以证明,dp[i][j]仅会由这三种状态转移而来.
所以不用判断条件,只需要求三者的min就好了.
边界条件
因为dp[i][j]是由dp[i-1][j-1],dp[i-1][j],dp[i][j-1]转移得到,所以如果想要进行二维循环1-i,1-j,我们需要知道dp[i][0]和dp[0][j],显然它们分别是2*i和2*j.
代码
一些代码细节问题:
排除空格我是用string读取一行然后一个一个erase的,复杂度是O(n^2),如果用char*顺序扫可以做到O(n),虽然因为dp最后的渐进复杂度都是O(n^2).
还是因为string,遍历dp时下标是1到i,1到j,这个时候只要将出现字符串的部分下标减一就好了.
/* LittleFall : Hello! */
#include <bits/stdc++.h>
using namespace std;
const int M = 1024;
int dp[M][M];
int main(void)
{
#ifdef _LITTLEFALL_
freopen("input.txt","r",stdin);
#endif
std::cin.sync_with_stdio(false);
string A,B;
int k;
getline(cin,A);
getline(cin,B);
cin >> k;
for(int i=A.size();i>=0;i--)
if(A[i]==' ') A.erase(i);
for(int i=B.size();i>=0;i--)
if(B[i]==' ') B.erase(i);
int la=A.size(),lb=B.size();
for(int i=0;i<=la;i++)
dp[i][0]=k*i;
for(int j=1;j<=lb;j++)
dp[0][j]=k*j;
for(int i=1;i<=la;i++)
for(int j=1;j<=lb;j++)
{
dp[i][j]=min(dp[i-1][j]+k,dp[i][j-1]+k);
dp[i][j]=min(dp[i][j],dp[i-1][j-1]+abs(A[i-1]-B[j-1]));
}
cout << dp[la][lb];
return 0;
}
启示
如果能知道一个状态会由哪些状态转移过来,那么就可以简单地进行min/max运算了,我将这个东西称为最优状态转移.