最长公共子序列 LCS
1:找最长公共子序列的长度
考虑最长公共子序列问题如何分解成子问题,设A=“a0,a1,…,am-1”,B=“b0,b1,…,bm-1”,并Z=“z0,z1,…,zk-1”为它们的最长公共子序列。不难证明有以下性质:
(1) 如果am-1=bn-1,则zk-1=am-1=bn-1,且“z0,z1,…,zk-2”是“a0,a1,…,am-2”和“b0,b1,…,bn-2”的一个最长公共子序列;
(2) 如果am-1!=bn-1,则若zk-1!=am-1,蕴涵“z0,z1,…,zk-1”是“a0,a1,…,am-2”和“b0,b1,…,bn-1”的一个最长公共子序列;
(3) 如果am-1!=bn-1,则若zk-1!=bn-1,蕴涵“z0,z1,…,zk-1”是“a0,a1,…,am-1”和“b0,b1,…,bn-2”的一个最长公共子序列。
这样,在找A和B的公共子序列时,如有am-1=bn-1,则进一步解决一个子问题,找“a0,a1,…,am-2”和“b0,b1,…,bm-2”的一个最长公共子序列;如果am-1!=bn-1,则要解决两个子问题,找出“a0,a1,…,am-2”和“b0,b1,…,bn-1”的一个最长公共子序列和找出“a0,a1,…,am-1”和“b0,b1,…,bn-2”的一个最长公共子序列,再取两者中较长者作为A和B的最长公共子序列。
求解:
引进一个二维数组c[][],用c[i][j]记录X[i]与Y[j] 的LCS 的长度,b[i][j]记录c[i][j]是通过哪一个子问题的值求得的,以决定搜索的方向。
我们是自底向上进行递推计算,那么在计算c[i,j]之前,c[i-1][j-1],c[i-1][j]与c[i][j-1]均已计算出来。此时我们根据X[i] = Y[j]还是X[i] != Y[j],就可以计算出c[i][j]。
#include<cstring>
#include<cmath>
#include<cstring>
#include<cstdio>
const int qq=1e3+10;
char x[qq],y[qq];
int dp[qq][qq];
int main()
{
while(~scanf("%s%s",x+1,y+1)){
x[0]=y[0]='.';
int len=strlen(x)>strlen(y)?strlen(x):strlen(y);
// printf("%d %d\n",strlen(x),strlen(y));
for(int i=0;i<=len;++i)
dp[i][0]=dp[0][i]=0;
for(int j,i=1;i<strlen(x);++i)
for(j=1;j<strlen(y);++j)
if(x[i]==y[j])
dp[i][j]=dp[i-1][j-1]+1;
else
dp[i][j]=dp[i-1][j]>dp[i][j-1]?dp[i-1][j]:dp[i][j-1];
printf("%d\n",dp[strlen(x)-1][strlen(y)-1]);
}
return 0;
}
最长上升公共子序列 LIS
dp思路
dp[ i ]以序列中第i个元素结尾的最长上升子序列的长度
那么状态转移方程为:if (a[i] > a[j]) dp[i] = MAX (dp[i], dp[j] + 1);
复杂度为O(n*n)
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int main()
{
int dps[10010],num[10010],n,i,j;
while(~scanf("%d",&n))
{
for(i=1; i<=n; i++)
{
scanf("%d",&num[i]);
dps[i]=1;
}
for(i=1; i<=n; i++)
for(j=1; j<i; j++)
{
if(num[i]>num[j])
dps[i]=max(dps[i],dps[j]+1);
}
int ans=0;
for(i=1; i<=n; i++)
{
ans=max(ans,dps[i]);
}
printf("%d\n",ans);
}
return 0;
}
最长上升子序列(LIS)的典型变形,熟悉的n^2的动归会超时。LIS问题可以优化为nlogn的算法。
定义 num[i]为原来的序列,a[i]为记录最长上升子序列,len是最长上升子序列的长度,a[len]是最长上升子序列中最末尾的元素,即最小的元素
最开始a[1]=num[1],len=1
然后判断如果num[i]>a[len],a[++len]=num[i]
如果num[i]<=a[len], 在a中找到一个位置满足a[j-1]<num[i]<a[j],将长度为j最长上升子序列的最小末尾元素用num[i]更新掉
a[j]=num[i];
最后输出长度len即可
这里可以用upper_bound函数来进行二分查找a[j]的位置
题目链接
http://acm.hdu.edu.cn/showproblem.php?pid=1950
#include<cstdio>
#include<algorithm>
using namespace std;
int main()
{
int t,n,num[40010],a[40010],i;
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
for(i=1;i<=n;i++)
scanf("%d",&num[i]);
int len=1;
a[1]=num[1];
for(i=2;i<=n;i++)
{
if(num[i]>a[len])
{
a[++len]=num[i];
continue;
}
int tt=upper_bound(a+1,a+len+1,num[i])-a;
a[tt]=num[i];
}
printf("%d\n",len);
}
return 0;
}