LCS (最大公共子序列)
朴素做法(n*n)
直接遍历全部的数组,遍历全部的状态,如果状态符合要求,就进行更新,否则进行继承。
#include<iostream>
using namespace std;
int dp[1001][1001],a1[2001],a2[2001],n,m;
int main()
{
//dp[i][j]表示两个串从头开始,直到第一个串的第i位
//和第二个串的第j位最多有多少个公共子元素
cin>>n>>m;
for(int i=1;i<=n;i++)scanf("%d",&a1[i]);
for(int i=1;i<=m;i++)scanf("%d",&a2[i]);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
if(a1[i]==a2[j])
dp[i][j]=max(dp[i][j],dp[i-1][j-1]+1);
//因为更新,所以++;
}
cout<<dp[n][m];
}
优化做法 (nlogn)
使用最大上升子序列的方法来做。
将b数组里的数用a中的位置来表示。
a b c d e
c b a d e
如果b数组中的数满足上升的话,即为a中的子序列,因为a中子序列恒定为上升子序列。
#include<iostream>
#include<cstdio>
using namespace std;
int a[100001],b[100001],map[100001],f[100001];
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++){scanf("%d",&a[i]);map[a[i]]=i;}
for(int i=1;i<=n;i++){scanf("%d",&b[i]);f[i]=0x7fffffff;}
int len=0;
f[0]=0;
for(int i=1;i<=n;i++)
{
int l=0,r=len,mid;
if(map[b[i]]>f[len])f[++len]=map[b[i]];
else
{
while(l<r)
{
mid=(l+r)/2;
if(f[mid]>map[b[i]])r=mid;
else l=mid+1;
}
f[l]=min(map[b[i]],f[l]);
}
}
cout<<len;
return 0
}
LIS(最大上升子序列)
朴素做法(n*n)
for(int i=1;i<=n;i++)
{
dp[i]=1;//初始化
for(int j=1;j<i;j++)//枚举i之前的每一个j
if(data[j]<data[i] && dp[i]<dp[j]+1)
//用if判断是否可以拼凑成上升子序列,
//并且判断当前状态是否优于之前枚举
//过的所有状态,如果是,则↓
dp[i]=dp[j]+1;//更新最优状态
}
优化做法(nlogn)
利用二分来进行优化,每次二分找到不小于其的第一个元素,并替换它。
int n;
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
f[i]=0x7fffffff;
//初始值要设为INF
/*原因很简单,每遇到一个新的元素时,就跟已经记录的f数组当前所记录的最长
上升子序列的末尾元素相比较:如果小于此元素,那么就不断向前找,直到找到
一个刚好比它大的元素,替换;反之如果大于,么填到末尾元素的下一个q,INF
就是为了方便向后替换啊!*/
}
f[1]=a[1];
int len=1;//通过记录f数组的有效位数,求得个数
/*因为上文中所提到我们有可能要不断向前寻找,
所以可以采用二分查找的策略,这便是将时间复杂
度降成nlogn级别的关键因素。*/
for(int i=2;i<=n;i++)
{
int l=0,r=len,mid;
if(a[i]>f[len])f[++len]=a[i];
//如果刚好大于末尾,暂时向后顺次填充
else
{
while(l<r)
{
mid=(l+r)/2;
if(f[mid]>a[i])r=mid;
//如果仍然小于之前所记录的最小末尾,那么不断
//向前寻找(因为是最长上升子序列,所以f数组必
//然满足单调)
else l=mid+1;
}
f[l]=min(a[i],f[l]);//更新最小末尾
}
}
cout<<len;
LIS 例题
P1091 [NOIP2004 提高组] 合唱队形
这题是可用朴素做法求两边最大上升子序列。
#include "bits/stdc++.h"
using namespace std;
int a[110];
int f[110],g[110];
int main()
{
int n;
cin>>n;
for (int i=1;i<=n;i++){
cin>>a[i];
}
//最多的人满足先升后降。
for (int i=1;i<=n;i++){
for (int j=0;j<i;j++){
if(a[i]>a[j]){
f[i]=max(f[i],f[j]+1);
}
}
}
a[n+1]=0;
for (int i=n;i>=1;i--){
for (int j=n+1;j>i;j--){
if(a[i]>a[j]){
g[i]=max(g[i],g[j]+1);
}
}
}
int ans=0;
for (int i=1;i<=n;i++){
ans=max(ans,f[i]+g[i]-1);
}
cout<<n-ans;
}
P1020 [NOIP1999 提高组] 导弹拦截
这是一道经典求最大上升子序列,和最大不上升子序列的题目。因为最大数据可达1e5 所以使用 nlogn 的做法求解。
#include <bits/stdc++.h>
using namespace std;
#define int long long
int a[100010],b[100010];
int c[100010];
signed main()
{
int cnt=0;
int x;
while(cin>>x){
a[++cnt]=x;
}
int res=0;
b[0]=1e9;
for (int i=1;i<=cnt;i++){
if(b[res]>=a[i]){
b[++res]=a[i];;
}
else {
int l=1,r=res;
while(l<r){
int mid=(l+r)>>1;
if(b[mid]<a[i]){
r=mid;
}
else {
l=mid+1;
}
}
b[l]=a[i];
}
}
cout<<res<<endl;
int m=0;
c[0]=-2e9;
for (int i=1;i<=cnt;i++){
if(a[i]>c[m]){
c[++m]=a[i];
}
else {
int l=1,r=m;
while(l<r){
int mid=(l+r)>>1;
if(c[mid]>=a[i]) r=mid;
else l=mid+1;
}
c[l]=a[i];
}
}
cout<<m<<endl;
}