子序列问题

(一)一个序列中的最长上升子序列(LISLIS)

n2做法 直接dp即可:

 for(int i=;i<=n;i++)
{
dp[i]=;//初始化
for(int j=;j<i;j++)//枚举i之前的每一个j
if(data[j]<data[i] && dp[i]<dp[j]+)
//用if判断是否可以拼凑成上升子序列,
//并且判断当前状态是否优于之前枚举
//过的所有状态,如果是,则↓
dp[i]=dp[j]+;//更新最优状态 }

n2

上述的方法 dp定义为以第i个数字为结尾的最长上升子序列长度 注意是以第i个数组为结尾 而不是前i个!!!!!!!!!! (我目前学的线性dp一般就是前i个 如背包  还有就是着重于第i个  这两种 虽然下面马上就是第三种了。。)

下一状态最优值=最优比较函数(已经记录的最优值,可以由先前状态得出的最优值)

——即动态规划具有 判断性继承思想

nlogn做法:

我们其实不难看出,对于n^2n2做法而言,其实就是暴力枚举:将每个状态都分别比较一遍。但其实有些没有必要的状态的枚举,导致浪费许多时间,当元素个数到了10^4-10^5104−105以上时,就已经超时了。而此时,我们可以通过另一种动态规划的方式来降低时间复杂度:

将原来的dp数组的存储由数值换成该序列中,上升子序列长度为i的上升子序列,的最小末尾数值

这其实就是一种几近贪心的思想:我们当前的上升子序列长度如果已经确定,那么如果这种长度的子序列的结尾元素越小,后面的元素就可以更方便地加入到这条我们臆测的、可作为结果、的上升子序列中。

int n;
cin>>n;
for(int i=;i<=n;i++)
{
cin>>a[i];
f[i]=0x7fffffff;
//初始值要设为INF
/*原因很简单,每遇到一个新的元素时,就跟已经记录的f数组当前所记录的最长
上升子序列的末尾元素相比较:如果小于此元素,那么就不断向前找,直到找到
一个刚好比它大的元素,替换;反之如果大于,么填到末尾元素的下一个q,INF
就是为了方便向后替换啊!*/
}
f[]=a[];
int len=;//通过记录f数组的有效位数,求得个数
/*因为上文中所提到我们有可能要不断向前寻找,
所以可以采用二分查找的策略,这便是将时间复杂
度降成nlogn级别的关键因素。*/
for(int i=;i<=n;i++)
{
int l=,r=len,mid;
if(a[i]>f[len])f[++len]=a[i];
//如果刚好大于末尾,暂时向后顺次填充
else
{
while(l<r)
{
mid=(l+r)/;
if(f[mid]>a[i])r=mid;
//如果仍然小于之前所记录的最小末尾,那么不断
//向前寻找(因为是最长上升子序列,所以f数组必
//然满足单调)
else l=mid+;
}
f[l]=min(a[i],f[l]);//更新最小末尾
}
}
cout<<len;

简化:

#define MAXN 40005

int arr[MAXN],ans[MAXN],len;

int main()
{
int T,p,i,j,k;
scanf("%d",&T);
while(T--){
scanf("%d",&p);
for(i=; i<=p; ++i)
scanf("%d",&arr[i]); ans[] = arr[];
len=;
for(i=; i<=p; ++i){
if(arr[i]>ans[len])
ans[++len]=arr[i];
else{
int pos=lower_bound(ans,ans+len,arr[i])-ans;
ans[pos] = arr[i];
}
}
}
printf("%d\n",len); return ;
}

终极简化:

#include<bits/stdc++.h>
using namespace std;
//input
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define RI(n) scanf("%d",&(n))
#define RII(n,m) scanf("%d%d",&n,&m);
#define RIII(n,m,k) scanf("%d%d%d",&n,&m,&k)
#define RS(s) scanf("%s",s);
#define LL long long
#define REP(i,N) for(int i=0;i<(N);i++)
#define CLR(A,v) memset(A,v,sizeof A)
//////////////////////////////////
const int INF=0x3fffffff;
int a[], dp[];
int main (void)
{
int n, ans = ; cin>>n;
fill(dp, dp + n, INF);
for (int i = ; i < n; i++){
cin>>a[i];
*lower_bound(dp, dp + n, a[i]) = a[i];
}
cout<<lower_bound(dp, dp +n, INF) - dp<<endl;
return ;
}

注意  上升序列用 low_bound 不下降序列用upper_bound

但是事实上,nlognnlogn做法偷了个懒,没有记录以每一个元素结尾的最长上升子序列长度。那么我们对于n^2n2的统计方案数,有很好想的如下代码(再对第一次的dpdp数组dpdp一次):

for(i = ; i <= N; i ++){
if(dp[i] == ) f[i] = ;
for(j = ; j <= N: j ++)
if(base[i] > base[j] && dp[j] == dp[i] - ) f[i] += f[j] ;
else if(base[i] == base[j] && dp[j] == dp[i]) f[i] = ;
if(f[i] == ans) res ++ ;
}

但是nlognnlogn呢?虽然好像也可以做,但是想的话会比较麻烦,在这里就暂时不讨论了qwq,但笔者说这件事的目的是为了再次论证一个观点:时间复杂度越高的算法越全能

输出路径

只要记录前驱,然后递归输出即可(也可以用栈的)

下面贴出n ^ 2n2的完整代码qwq

#include <iostream>
using namespace std;
const int MAXN = + ;
int n, data[MAXN];
int dp[MAXN];
int from[MAXN];
void output(int x)
{
if(!x)return;
output(from[x]);
cout<<data[x]<<" ";
//迭代输出
}
int main()
{
cin>>n;
for(int i=;i<=n;i++)cin>>data[i]; // DP
for(int i=;i<=n;i++)
{
dp[i]=;
from[i]=;
for(int j=;j<i;j++)
if(data[j]<data[i] && dp[i]<dp[j]+)
{
dp[i]=dp[j]+;
from[i]=j;//逐个记录前驱
}
} int ans=dp[], pos=;
for(int i=;i<=n;i++)
if(ans<dp[i])
{
ans=dp[i];
pos=i;//由于需要递归输出
//所以要记录最长上升子序列的最后一
//个元素,来不断回溯出路径来
}
cout<<ans<<endl;
output(pos); return ;

(二)两个序列中的最长公共子序列(LCSLCS)

1、譬如给定2个序列:

1 2 3 4 5

3 2 1 4 5

试求出最长的公共子序列。

qwqqwq显然长度是33,包含3 \ \ 4 \ \ 53  4  5 三个元素(不唯一)

解析:我们可以用dp[i][j]dp[i][j]来表示第一个串的前ii位,第二个串的前j位的LCSLCS的长度,那么我们是很容易想到状态转移方程的:

如果当前的A1[i]A1[i]和A2[j]A2[j]相同(即是有新的公共元素) 那么

dp[ i ] [ j ] = max(dp[ i ] [ j ], dp[ i-1 ] [ j-1 ] + 1);dp[i][j]=max(dp[i][j],dp[i−1][j−1]+1);

如果不相同,即无法更新公共元素,考虑继承:

dp[ i ] [ j ] = max(dp[ i-1 ][ j ] , dp[ i ][ j-1 ]dp[i][j]=max(dp[i−1][j],dp[i][j−1]

#include<iostream>
using namespace std;
int dp[][],a1[],a2[],n,m;
int main()
{
//dp[i][j]表示两个串从头开始,直到第一个串的第i位
//和第二个串的第j位最多有多少个公共子元素
cin>>n>>m;
for(int i=;i<=n;i++)scanf("%d",&a1[i]);
for(int i=;i<=m;i++)scanf("%d",&a2[i]);
for(int i=;i<=n;i++)
for(int j=;j<=m;j++)
{
dp[i][j]=max(dp[i-][j],dp[i][j-]);
if(a1[i]==a2[j])
dp[i][j]=max(dp[i][j],dp[i-][j-]+);
//因为更新,所以++;
}
cout<<dp[n][m];
}

而对于洛谷P1439而言(两个序列为1-n的两个全排列),不仅是卡上面的朴素算法,也考察到了全排列的性质:

对于这个题而言,朴素算法是n^2n2的,会被10^5105卡死,所以我们可以考虑nlognnlogn的做法:

因为两个序列都是1~n1 n的全排列,那么两个序列元素互异且相同,也就是说只是位置不同罢了,那么我们通过一个mapmap数组将AA序列的数字在BB序列中的位置表示出来——

因为最长公共子序列是按位向后比对的,所以a序列每个元素在b序列中的位置如果递增,就说明b中的这个数在a中的这个数整体位置偏后,可以考虑纳入LCSLCS——那么就可以转变成nlognnlogn求用来记录新的位置的map数组中的LISLIS。

上述为离散化处理

#include<iostream>
#include<cstdio>
using namespace std;
int a[],b[],map[],f[];
int main()
{
int n;
cin>>n;
for(int i=;i<=n;i++){scanf("%d",&a[i]);map[a[i]]=i;}
for(int i=;i<=n;i++){scanf("%d",&b[i]);f[i]=0x7fffffff;}
int len=;
f[]=;
for(int i=;i<=n;i++)
{
int l=,r=len,mid;
if(map[b[i]]>f[len])f[++len]=map[b[i]];
else
{
while(l<r)
{
mid=(l+r)/;
if(f[mid]>map[b[i]])r=mid;
else l=mid+;
}
f[l]=min(map[b[i]],f[l]);
}
}
cout<<len;
return
}

最长上升序列 LCS LIS的更多相关文章

  1. 最长上升序列(Lis)

    Description A numeric sequence of ai is ordered if a1 < a2 < ... < aN. Let the subsequence ...

  2. LIS(最长的序列)和LCS(最长公共子)总结

    LIS(最长递增子序列)和LCS(最长公共子序列)的总结 最长公共子序列(LCS):O(n^2) 两个for循环让两个字符串按位的匹配:i in range(1, len1) j in range(1 ...

  3. 最长公共子序列-LCS问题 (LCS与LIS在特殊条件下的转换) [洛谷1439]

    题目描述 给出1-n的两个排列P1和P2,求它们的最长公共子序列. 输入 第一行是一个数n, 接下来两行,每行为n个数,为自然数1-n的一个排列. 输出 一个数,即最长公共子序列的长度 输入样例 5 ...

  4. 最长公共子序列(LCS)、最长递增子序列(LIS)、最长递增公共子序列(LICS)

    最长公共子序列(LCS) [问题] 求两字符序列的最长公共字符子序列 问题描述:字符序列的子序列是指从给定字符序列中随意地(不一定连续)去掉若干个字符(可能一个也不去掉)后所形成的字符序列.令给定的字 ...

  5. (LIS)最长上升序列(DP+二分优化)

    求一个数列的最长上升序列 动态规划法:O(n^2) //DP int LIS(int a[], int n) { int DP[n]; int Cnt=-1; memset(DP, 0, sizeof ...

  6. XHXJ's LIS HDU - 4352 最长递增序列&数位dp

    代码+题解: 1 //题意: 2 //输出在区间[li,ri]中有多少个数是满足这个要求的:这个数的最长递增序列长度等于k 3 //注意是最长序列,可不是子串.子序列是不用紧挨着的 4 // 5 // ...

  7. LCS,LIS,LCIS学习

    for(int i = 1;i <= n;i++) { int dpmax = 0; for(int j = 1;j <= m;j++) { dp[i][j] = dp[i-1][j]; ...

  8. 最长递增子序列(LIS)(转)

    最长递增子序列(LIS)   本博文转自作者:Yx.Ac   文章来源:勇幸|Thinking (http://www.ahathinking.com)   --- 最长递增子序列又叫做最长上升子序列 ...

  9. LCS/LIS/LCIS 模板总结

    /************************* LCS/LIS/LCIs模板总结: *************************/ /*************************** ...

随机推荐

  1. 【BZOJ2749】【HAOI2012】外星人[欧拉函数]

    外星人 Time Limit: 3 Sec  Memory Limit: 128 MB[Submit][Status][Discuss] Description Input   Output 输出te ...

  2. C++中返回值

    函数的返回值用于初始化在调用函数是创建的临时对象. 1.返回值为非引用类型: 会将函数的返回值复制给临时对象.跟实参初始化形参的方式一样. 2.返回值为引用类型: 没有复制返回值,返回的是对象本身.返 ...

  3. PyCharm的调试

    1.设置断点 2.debug模式运行    F8      下一行代码 查看当前位置所有局部变量:print(locals()) 查看全局变量:                      print( ...

  4. JavaScript之12306自动刷新车票[待完善]

    function refresh(){ var search_btn = document.getElementById("query_ticket"); var result_t ...

  5. c# 匿名函数

    int t(){    Func<int> m=()=>3;    return m()+m();}

  6. luogu P4778 Counting swaps

    计数套路题?但是我连套路都不会,,, 拿到这道题我一脸蒙彼,,,感谢@poorpool 大佬的博客的指点 先将第\(i\)位上的数字\(p_i\)向\(i\)连无向边,然后构成了一个有若干环组成的无向 ...

  7. PHP - CentOS下开发运行环境搭建(Apache+PHP+MySQL+FTP)

    本文介绍如何在 Linux下搭建一个 PHP 环境.其中 Linux 系统使用是 CentOS 7.3,部署在阿里云服务器上.   1,连接登录服务器 拿到服务器的 ip.初始密码以后.我们先通过远程 ...

  8. 2017-2018-2 20155303『网络对抗技术』Exp1:PC平台逆向破解

    2017-2018-2 『网络对抗技术』Exp1:PC平台逆向破解 --------CONTENTS-------- 1. 逆向及Bof基础实践说明 2. 直接修改程序机器指令,改变程序执行流程 3. ...

  9. C++ Boost库简介(转载)

    boost是一个准标准库,相当于STL的延续和扩充,它的设计理念和STL比较接近,都是利用泛型让复用达到最大化.不过对比STL,boost更加实用.STL集中在算法部分,而boost包含了不少工具类, ...

  10. CentOS修改编码方式为zh_CN.UTF-8

    1.查看系统是否支持简体中文 locale -a 2.修改编码方式 vim /etc/sysconfig/i18n 将文件内容修改为下面文本: LANG="zh_CN.UTF-8" ...