听说下雨天,子序列和孤单的你更配哦~

一、\(DP\)的意义以及线性动规简介

动态规划自古以来是\(DALAO\)凌虐萌新的分水岭,但有些OIer认为并没有这么重要——会打暴力,大不了记忆化。但是其实,动态规划学得好不好,可以彰显出一个\(OIer\)的基本素养——能否富有逻辑地思考一些问题,以及更重要的——能否将数学、算筹学(决策学)、数据结构合并成一个整体并且将其合理运用\(qwq\)

而我们首先要了解的,便是综合难度在所有动规题里最为简单的线性动规了。线性动规既是一切动规的基础,同时也可以广泛解决生活中的各项问题——比如在我们所在的三维世界里,四维的时间就是不可逆式线性,比如我们需要决策在相同的时间内做价值尽量大的事情,该如何决策,最优解是什么——这就引出了动态规划的真正含义:

在一个困难的嵌套决策链中,决策出最优解。

二、动态规划性质浅谈

首先,动态规划和递推有些相似(尤其是线性动规),但是不同于递推的是:

递推求出的是数据,所以只是针对数据进行操作;而动态规划求出的是最优状态,所以必然也是针对状态的操作,而状态自然可以出现在最优解中,也可以不出现——这便是决策的特性(布尔性)。

其次,由于每个状态均可以由之前的状态演变形成,所以动态规划有可推导性,但同时,动态规划也有无后效性,即每个当前状态会且仅会决策出下一状态,而不直接对未来的所有状态负责,可以浅显的理解为——

_ \(\mathcal{Future \ \ never \ \ has \ \ to \ \ do \ \ with \ \ past \ \ time \ \ ,but \ \ present \ \ }.\)_

现在决定未来,未来与过去无关。

三、扯正题——子序列问题

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

例:由6个数,分别是: 1 7 6 2 3 4,求最长上升子序列。

评析:首先,我们要理解什么叫做最长上升子序列:1、最长上升子序列的元素不一定相邻 2、最长上升子序列一定是原序列的子集。所以这个例子中的\(LIS\)就是:1 2 3 4,共4个

1、\(n^2\)做法

首先我们要知道,对于每一个元素来说,最长上升子序列就是其本身。那我们便可以维护一个\(dp\)数组,使得\(dp[i]\)表示以第\(i\)元素为结尾的最长上升子序列长度,那么对于每一个\(dp[i]\)而言,初始值即为\(1\);

那么dp数组怎么求呢?我们可以对于每一个\(i\),枚举在\(i\)之前的每一个元素\(j\),然后对于每一个\(dp[j]\),如果元素\(i\)大于元素\(j\),那么就可以考虑继承,而最优解的得出则是依靠对于每一个继承而来的\(dp\)值,取\(max\).

    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;//更新最优状态 

    }

最后,因为我们对于\(dp\)数组的定义是到i为止的最长上升子序列长度,所以我们最后对于整个序列,只需要输出\(dp[n]\)(\(n\)为元素个数)即可。

从这个题我们也不难看出,状态转移方程可以如此定义:

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

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

2、\(nlogn\) 做法

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

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

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

qwq一定要好好看注释啊!

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;


\(Another \ \ Situation\)

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

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

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


\(3\)、输出路径

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

下面贴出\(n ^ 2\)的完整代码qwq

#include <iostream>
using namespace std;
const int MAXN = 1000 + 10;
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=1;i<=n;i++)cin>>data[i];

    // DP
    for(int i=1;i<=n;i++)
    {
        dp[i]=1;
        from[i]=0;
        for(int j=1;j<i;j++)
        if(data[j]<data[i] && dp[i]<dp[j]+1)
        {
            dp[i]=dp[j]+1;
            from[i]=j;//逐个记录前驱
        }
    }

    int ans=dp[1], pos=1;
    for(int i=1;i<=n;i++)
        if(ans<dp[i])
        {
            ans=dp[i];
            pos=i;//由于需要递归输出
    //所以要记录最长上升子序列的最后一
    //个元素,来不断回溯出路径来
        }
    cout<<ans<<endl;
    output(pos);

    return 0;
}

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

1、譬如给定2个序列:

1 2 3 4 5

3 2 1 4 5

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

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

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

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

那么

\(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 ] $

那么代码:

#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];
}

\(2\)、而对于洛谷\(P1439\)而言,不仅是卡上面的朴素算法,也考察到了全排列的性质:

对于这个题而言,朴素算法是\(n^2\)的,会被\(10^5\)卡死,所以我们可以考虑\(nlogn\)的做法:

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

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

最后贴\(AC\)代码:

#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
}

_ \(\mathcal{Although \ \ there‘re \ \ difficulties \ \ ahead \ \ of \ \ us \ \ , \ \ remember \ \ :}\) _

就算出走半生,归来仍要是少年

原文地址:https://www.cnblogs.com/pks-t/p/9315266.html

时间: 2024-11-29 04:01:30

听说下雨天,子序列和孤单的你更配哦~的相关文章

OSChina 周四乱弹 —— 听说圣诞节和单身狗更配哦

周四,剩单快乐! 周三又是个不寻常的日子,西方节日总是充斥着各种故事 昨天由@叶秀兰  童鞋引发了众多 OSCer 对@红薯  和 @永和  之间爱恨情仇的各种纠结故事,这里小小编整理了一个全集,供大家欣赏,希望能给大家带来一些些灵感-(永和和红薯不得不说的爱恨情仇:http://my.oschina.net/xxiaobian/blog/360230) 也会激发 N 多 OSCer 的情怀! @blindcat  :1厨1卫1人住,1菜1汤1人吃,1枕1被1人睡,1来1去1人过,1衣1袜1人洗

我听说,Prometheus与技术中台更配哦

前言 随着容器技术这几年的迅速发展,Kubernetes已经逐渐成为云生态圈CNCF(Cloud Native Computing Foundation)当之无愧的老大.而Prometheus稳坐CNCF基金会的"第二把交椅",已然成为Kubernetes群集监控系统的必要组成部分. 现在有越来越多的企业,有意向或正在由传统技术基础架构向云环境迁移.在开源监控系统方面,企业有Zabbix.Prometheus等系统可以选择.不可否认,Zabbix的产品成熟度很高,同时也与时俱进,且仍然

在Go里使用OpenCL,"下雨天压榨GPU更配哦"

原文标题: 能在Go里用GPU运算的OpenCL语言绑定包 首先网页访问https://github.com/pseudomind/go-opencl/了解一下,然后下载它 C:\go\src\src>go get github.com/pseudomind/go-opencl/cl 再搜索一下你的OpenCL.dll文件,把它复制到gcc编译器的lib目录里比如我在c盘搜索出opencl.dll,把它复制到了C:\TDM-GCC-32\lib\里 用LiteIDE打开https://githu

小白有问题-下雨天给linux装adobe flash player更配

上班出门还没下雨天气闷热,现在的外面下的却是倾盆大雨.还好出门带了伞,内心还是快乐的. 上班我们都是用的Debian系统,平时没事上上网偶尔会遇到提示没安装flash的问题,正好现在没啥事,就打算把它给装上. 现在自己用的浏览器是firefox,从adobe官网下载下来了安装包,恰巧里面也有安装文档,于是便照着安装: 1. 解压tar.gz安装包,现在也不用输入命令了图形化界面就可以解压,还不知道怎么解压的可以百度一下,或者翻翻我转载的文章 2.解压完毕后,我们会发现有libflashplaye

[开源].NET CORE与MySql更配, MySqlSugar ORM框架 3.x

MySqlSugar 3.X API 作为支持.NET CORE 为数不多的ORM之一,除了具有优越的性能外,还拥有强大的功能,不只是满足你的增,删,查和改.实质上拥有更多你想像不到的功能,当你需要实现某个功能时会发现有这个功能太棒了. 所有版本 ASP.NET 4.0+ MSSQL https://github.com/sunkaixuan/SqlSugar ASP.NET CORE MSSQL https://github.com/sunkaixuan/ASP_NET_CORE_ORM_Sq

C#和NewSQL更配 —— CockroachDB入门(可能是C#下的全网首发)

阅读目录 CockroachDB是什么 环境部署 实战 性能测试 结语 一.CockroachDB是什么 CockroachDB(https://www.cockroachlabs.com)是Google备受瞩目的Spanner的开源模仿,承诺提供一种高存活性.强一致性,可横向扩展的SQL数据库.主要的设计目标是全球一致性和可靠性,从蟑螂(cockroach)的命名上是就能看出这点 [ 打不死的小强:) ].Cockroach节点是均衡的,其设计目标是同质部署(只有一个二进制包)且最小配置.Co

rest-assured : Restful API 测试利器 - 真正的黑盒单元测试(跟Spring-Boot更配哦)

这里先贴一下IBM上的介绍 http://www.ibm.com/developerworks/cn/java/j-lo-rest-assured/index.html Java 程序员常常借助于 JUnit 来测试自己的 REST API,不,应该这样说,Java 程序员常常借助于 JUnit 来测试 REST API 的实现!从某种角度来说,这是一种“白盒测试”,Java 程序员清楚地知道正在测试的是哪个类.哪个方法,而不是从用户的角度出发,测试的是哪个 REST API. 不废话,直接给S

Ink 和 TravisCI 更配哦

前言 去年还是前年,无意间接触到ink,看到是用go写的,非常小巧和精简,于是乎fork了下,还整了个供ink用的docker镜像``. 不过那时候热衷于折腾博客...结果也没折腾出什么来, 今天整理项目的时候,发现有个ink,说实话,都忘了是什么东西了...点开看看了,于是有了下文. 过程 搞定了之后,回头留笔墨时,发现自己还是个懒人,细细道来是不可能了,还是划下重点好了. 初始化一个仓库,里面仅包含ink template & 自己写的.md文档,然后git push即可(需要手动操作的)

那些年我们收藏的网站(1 )

欢迎转载. 寒假终于来了.终于可以轻松愉快的玩耍了.和大家分享一些学习类的网站,另外分享一些资源类的网站,至于翻墙什么带有被河蟹词语的网站就不和大家分享了.以学习为目的,和大家共同交流讨论促进进步.我猜,一定会有大量广告党前去拜访,希望站长不会对我菊花下手就好. 按我收藏的时间排序的.从现在的往前慢慢和大家分享!下面好像可以带个附件,等会顺手上传. DB2运维中国 http://www.db2china.net/ 什么数据库啦.Linux运维啦,感觉是个新站呢,以前没注意,最近看Oracle的一