问题:计算两个序列的最长公共子序列。
分析:两个序列分别为S[],T[],
设dp[i][j]
表示S的i位和T的j位之前的最长公共子序列的长度,则有:
其中same(i,j)表示S的第i位与T的第j位完全相同时为1,否则为0。
#include <iostream>
#include <string.h>
#include <stdio.h>
using namespace std;
const int N = 1005;
int dp[N][N];
int f[N][N];
char S[N];
char T[N];
int LCS(char S[],int m,char T[],int n)
{
for(int i=1;i<=n;i++)
dp[0][i] = 0;
for(int i=1;i<=m;i++)
dp[i][0] = 0;
for(int i=1;i<=m;i++)
{
for(int j=1;j<=n;j++)
{
if(S[i-1] == T[j-1])
{
dp[i][j] = dp[i-1][j-1] + 1;
f[i][j] = 0;
}
else if(dp[i-1][j] >= dp[i][j-1])
{
dp[i][j] = dp[i-1][j];
f[i][j] = 1;
}
else
{
dp[i][j] = dp[i][j-1];
f[i][j] = -1;
}
}
}
return dp[m][n];
}
void Print(char S[],int i,int j)
{
if(i == 0 || j == 0) return;
if(f[i][j] == 0)
{
Print(S,i-1,j-1);
printf("%c",S[i-1]);
}
else if(f[i][j] == 1)
Print(S,i-1,j);
else
Print(S,i,j-1);
}
int main()
{
while(~scanf("%s%s",S,T))
{
int m = strlen(S);
int n = strlen(T);
LCS(S,m,T,n);
Print(S,m,n);
puts("");
printf("%d\n",dp[m][n]);
}
return 0;
}
最长公共上升子序列(LCIS)
分析:设状态dp[i][j]表示以串S的前i个字符和串T的前j个字符且以T[j]为结尾构成的LCIS的长度。那么状态转移方程为:
经过优化,时间可以降为O(n^2),空间可以降为O(n)。
//下标均从1开始
int LCIS(int S[],int m,int T[],int n)
{
int ans;
memset(dp,0,sizeof(dp));
for(int i=1;i<=m;i++)
{
ans = 0;
for(int j=1;j<=n;j++)
{
if(S[i] > T[j] && ans < dp[j]) ans = dp[j];
if(S[i] == T[j]) dp[j] = ans + 1;
}
}
ans = 0;
for(int i=1;i<=n;i++)
ans = max(ans,dp[i]);
return ans;
}
分析:本题的难点是要求出最长公共上升子序列的长度并输出这个最长公共上升子序列,所以难点就在于记录路径。
#include <iostream>
#include <string.h>
#include <stdio.h>
using namespace std;
const int N = 1005;
struct Path
{
int x,y;
};
Path p[N][N];
int dp[N][N];
int S[N],T[N];
int Save[N];
int n,m,R;
int LCIS(int S[],int m,int T[],int n)
{
int ans;
memset(dp,0,sizeof(dp));
for(int i=1;i<=m;i++)
{
ans = 0;
int x = 0;
int y = 0;
for(int j=1;j<=n;j++)
{
dp[i][j] = dp[i-1][j];
p[i][j].x = i-1;
p[i][j].y = j;
if(S[i] > T[j] && ans < dp[i-1][j])
{
ans = dp[i-1][j];
x = i-1;
y = j;
}
if(S[i] == T[j])
{
dp[i][j] = ans + 1;
p[i][j].x = x;
p[i][j].y = y;
}
}
}
ans = 0;
R = 0;
for(int i=1;i<=n;i++)
{
if(ans < dp[m][i])
{
ans = dp[m][i];
R = i;
}
}
return ans;
}
void Print()
{
int cnt = 0;
int tx = m;
int ty = R;
while(dp[tx][ty])
{
int tmpx = p[tx][ty].x;
int tmpy = p[tx][ty].y;
if(dp[tmpx][tmpy] != dp[tx][ty])
Save[cnt++] = T[ty];
tx = tmpx;
ty = tmpy;
}
for(int i=cnt-1;i>=0;i--)
printf("%d ",Save[i]);
puts("");
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
scanf("%d",&m);
for(int i=1;i<=m;i++)
scanf("%d",&S[i]);
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&T[i]);
printf("%d\n",LCIS(S,m,T,n));
Print();
if(t) puts("");
}
return 0;
}
题意:给定一个字符串,求至少添加多少个字符使它成为回文串。
分析:假设原串为a,我们设串a的反转串为b,对串a和串b求最长公共子序列,假设最长公共子序列长度为ans,那么至少
需要添加strlen(a) - ans个字符使串a为回文串。