这是我的第一篇博客,接触ACM也快半年了,一直没写过一篇像样的解题报告,惭愧。
题目链接 http://poj.org/problem?id=2250
题意很简单,给出n组case,每组case由两部分组成,分别包含若干个单词,都以“#”当结束标志,要求输出最长的子序列。
其实在之前也做过类似的题目,给出两个字符串,问最长子序列的长度,很基础的一道dp,这道题可以说是那道题的升级版,一个是把字符换成了字符串,另一个难点是要求输出最长的子序列,那么就和原来的题目不同了,不是一个dp二维数组就可以的了,必须要记录路径,我的做法可能比较弱,但我感觉应该比较容易理解和接受吧,下面说一下我的思路和做法。
输入就不说了,首先是要求出最长子序列的长度,dp思想,dp[i][j]代表第一部分的前i个单词和第二部分的前j个单词的最长子序列,状态转移方程是如果第i个单词和第j个单词相同,dp[i][j]=dp[i-1][j-1]+1,否则dp[i][j]=max(dp[i-1][j],dp[i][j-1]);这样循环下去,最后结果就是最长子序列的长度,然后要做的就是记录路劲,我的做法是开一个三维数组ans[i][j][k],ans[i][j]数组中存储第一部分前i个和第二部分前j个相同的单词在第一部分中的位置,开始初始化都是空的,如果第一部分第i个和第二部分第j个单词相同,则把ans[i-1][j-1]中的元素都拷过来,一共有dp[i][j]-1个元素,再在第dp[i][j]个位置上加入i,如果单词不相同,看dp[i-1][j]和dp[i][j-1]谁更大,就把对应的ans的元素都拷到ans[i][j]中,最后,根据数组ans[第一部分单词数][第二部分单词数]之中的元素把第一部分中相应位置的单词依次输出来就可以了。
本来以为可能会超时,谁知道结果0ms,呵呵了。。。
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
int ans[105][105][105],flag[105][105];
char s1[105][35],s2[105][35];
int len1,len2;
void dp(){
memset(ans,0,sizeof(ans));
memset(flag,0,sizeof(flag));
for(int i=1;i<len1;i++){
for(int j=1;j<len2;j++){
if(!strcmp(s1[i],s2[j])){
for(int k=0;k<flag[i-1][j-1];k++)
ans[i][j][k]=ans[i-1][j-1][k];
flag[i][j]=flag[i-1][j-1]+1;
ans[i][j][flag[i][j]-1]=i;
}
else{
if(flag[i-1][j]>flag[i][j-1]){
flag[i][j]=flag[i-1][j];
for(int k=0;k<flag[i-1][j];k++)
ans[i][j][k]=ans[i-1][j][k];
}
else{
flag[i][j]=flag[i][j-1];
for(int k=0;k<flag[i][j-1];k++)
ans[i][j][k]=ans[i][j-1][k];
}
}
}
}
}
void print(){
for(int i=0;i<flag[len1-1][len2-1];i++)
if(i) printf(" %s",s1[ans[len1-1][len2-1][i]]);
else printf("%s",s1[ans[len1-1][len2-1][i]]);
printf("\n");
}
int main()
{
while(scanf("%s",&s1[1])!=-1){
len1=2;len2=1;
while(scanf("%s",&s1[len1])&&strcmp(s1[len1],"#"))
len1++;
while(scanf("%s",&s2[len2])&&strcmp(s2[len2],"#"))
len2++;
dp();
print();
}
return 0;
}