首页 > 其他分享 >学不会的动态规划——子序列篇

学不会的动态规划——子序列篇

时间:2023-10-30 16:57:05浏览次数:29  
标签:int long low ans 序列 动态 规划 dp

前言

感觉摆烂好久了,其实好像也没有摆烂,只是没有学新东西了,之前打算死磕网络流的,但是感觉对我们队目前来说用处不大,就算南京站真的出了,99.9999%的概率写不来,所以就去练思维了。但是好像也并没有怎么练到,被大量的作业绑架了呜呜呜QAQ。感觉dp方面还是太弱了,最后挣扎一下。

一些概念

最长上升子序列是指,从原序列中按顺序取出一些数字排在一起,这些数字是逐渐增大的。

子串,串中任意个连续的字符组成的子序列称为该串的子串。

子序列定义:给定序列,从中任意地选取无限项,按照原来的顺序组成的序列称为序列的一个子序列。

最长上升子序列(LIS)

以洛谷的B3637 最长上升子序列,介绍两种不同时间复杂度的写法

解法1:朴素dp O(\(n ^ 2\))

我们不妨定义一个dp[i]用来记录以a[i]结尾的子序列的最大长度。

遍历每一个位置,对于每一个位置的dp[i]的值,我们可以从i之前的位置转移过来,举个例子

1 2 3 4 6 9
我们现在在4的位置,可以从1转移过来,也可以从2转移过来……能从在6之前,任何一个比6小的元素转移过来

不难推出状态转移方程:
$ if(a[i] > a[j]) dp[i] = max(dp[i] , dp[j] + 1); $

因为子序列最少都能有一个元素,所以将dp[i]初始化为1,那么我们不难得到下面的代码

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN = 1000 + 10;
int n, a[MAXN];
int dp[MAXN];
int pre[MAXN];

void output(int x) {
    if (!x)return;
    output(pre[x]);
    cout << a[x] << " ";
    //迭代输出 
}

signed main() {
    cin >> n;
    for (int i = 1; i <= n; i++)cin >> a[i];

    // DP
    for (int i = 1; i <= n; i++) {
        dp[i] = 1;
        pre[i] = 0;
        for (int j = 1; j < i; j++)
            if (a[j] < a[i] && dp[i] < dp[j] + 1) {
                dp[i] = dp[j] + 1;
                pre[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;
}

解法2:贪心 + 二分 O(nlogn)

举个例子:
3 1 2 1 8 5 6

长度为1:3 1 2 8 5 6
长度为2:3,8  3,5  3,6  1,2  1,8  1,5  1,6
长度为3:3,5,6   1,2,8   1,2,5   1,2,6
长度为4:1,2,5,6

对于一个最长上升子序列,显然,元素越小越有利于后面元素的增长。

我们不妨维护一个low[i]数组,用来记录,长度为i的序列中最后一个元素的可能的最小值。

如果我们碰到了一个\(a[i]\)满足\(low[t] < a [i] < low[t + 1]\),说明这个\(a[i]\)可以接在长度为t的子序列后面,而\(a[i] < q[k + 1]\),\(a[i]\)可以作为长度为\(t + 1\)的最后一个元素的最小值。所以我们更新最小值\(low[t + 1] = a[i]\)。

易证low[i]是一个升序序列,我们可以用二分法快速找到a[i]的位置。

#include <bits/stdc++.h>
#define int long long
using namespace std;
void solve(){
    int n;
    cin >> n;
    int a[n + 1] , low[n + 1];
    for(int i = 1 ; i <= n ; i ++) cin >> a[i] , low[i] = 0;
    int ans = 1;
    low[ans] = a[1];
    for(int i = 1 ; i <= n ; i ++){
        if(low[ans] < a[i]){
            low[++ ans] = a[i];
        }
        auto t = lower_bound(low + 1 , low + 1 + ans , a[i]);//长度为ans
        *t = a[i];
    }

    cout << ans << '\n';
    return ;
}
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);

    int t = 1;
//    cin >> t;
    while (t --){
        solve();
    }
    return 0;
}

最长公共子序列(LIS)

解法1:朴素dp O(\(n ^ 2\))

我们不妨定义一个二维数组dp[i][j],表示第一个序列的走到第i个位置,第二个序列走到第j个位置时公共子序列的长度。

容易得到下面的状态转移方程:
if(a[i] == b[j]){
dp[i][j] = max(dp[i][j] , dp[i - 1][j - 1] + 1);
}else{
dp[i][j] = max(dp[i - 1][j] , dp[i][j - 1]);
}

不难得到以下算法:

#include <bits/stdc++.h>
#define int long long
using namespace std;
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);

    int n;
    cin >> n;
    int a[n + 1] , b[n + 1] , dp[n + 1][n + 1];
    for(int i = 0 ; i <= n ; i ++){
        for(int j = 0 ; j <= n ; j ++){
            dp[i][j] = 0;
        }
    }
    for(int i = 1 ; i <= n ; i ++) cin >> a[i];
    for(int i = 1 ; i <= n ; i ++) cin >> b[i];

    for(int i = 1;  i <= n ; i ++){
        for(int j = 1 ; j <= n ; j ++){
            if(a[i] == b[j]){
                dp[i][j] = max(dp[i][j] , dp[i - 1][j - 1] + 1);
            }else{
                dp[i][j] = max(dp[i - 1][j] , dp[i][j - 1]);
            }
        }
    }
    cout << dp[n][n] << "\n";
    return 0;
}

解法2:map优化 O(\(nlogn\))

对于洛谷P1439 【模板】最长公共子序列,\(10^5\)的数据量在O(\(n ^ 2\))的事件复杂度下,显然超时。由于全排列的特殊性,我们可以考虑\(nlogn\)的做法:

因为两个序列都是1-n的全排列,那么两个序列元素都有唯一对应的位置。

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

不难得到下面的代码

#include <bits/stdc++.h>
#define int long long
using namespace std;
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);

    int n;
    cin >> n;
    int a[n + 1] , b[n + 1] , arr[n + 1];
    map<int , int> mp;

    for(int i = 1 ; i <= n ; i ++) cin >> a[i] , mp[a[i]] = i;
    for(int i = 1 ; i <= n ; i ++) cin >> b[i] , arr[i] = mp[b[i]];

    int low[n + 1];
    int ans = 1;
    memset(low , 0 , sizeof(low));
    low[ans] = arr[1];
    for(int i = 1; i <= n ; i ++){
        if(arr[i] > low[ans]){
            low[++ ans] = arr[i];
        }else{
            auto t = lower_bound(low + 1 , low + 1 + ans , arr[i]);
            *t = arr[i];
        }
    }

    cout << ans << "\n";
    return 0;
}

后记

暂时先到这里吧,虽然好像也没有写什么东西,写的也不清楚,但还是幸苦自己了。

标签:int,long,low,ans,序列,动态,规划,dp
From: https://www.cnblogs.com/clearTea/p/17790348.html

相关文章

  • el-table选中效果及动态修改
    项目有个需求,是点击关联账户,弹窗显示已经关联的,而且表格上还要勾上效果: 这里的交互有两条线:1.勾选表格内容,上方标签显示和隐藏2.删除上方标签,表格中的该条数据去除选中效果......
  • P4309 [TJOI2013] 最长上升子序列题解
    P4309[TJOI2013]最长上升子序列题解正文单调队列?单调锤子队列!!本题的操作可以省略成:单点修改区间查询好极了,此时我们有两种选择:线段树和树状数组,(平衡树,真不会,下一位因为不需要其他操作,所以我们还是选择更小巧更可爱的树状数组吧。关于vectorvector的insert操作支......
  • 一种动态实现核隔离的方法
    本文分享自天翼云开发者社区《一种动态实现核隔离的方法》,作者:y****n一、技术背景相关概念:核隔离:指定的cpu核心只参与最低限度的OS内核计算; DPDK(Dateplanedevelopmentkit):是一个用来进行包数据处理加速的软件库。Cpu亲和性:进程要在某个给定的CPU上尽量长时间地运行而不......
  • JS动态在父元素里追加元素——insertAdjacentHTML
    insertAdjacentHTML() 方法将指定的文本解析为 Element 元素,并将结果节点插入到DOM树中的指定位置。它不会重新解析它正在使用的元素,因此它不会破坏元素内的现有元素。这避免了额外的序列化步骤,使其比直接使用innerHTML操作更快。 element.insertAdjacentHTML(position,......
  • leetcode(力扣) 128. 最长连续序列(哈希)
    文章目录题目描述思路分析完整代码题目描述给定一个未排序的整数数组nums,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。请你设计并实现时间复杂度为O(n)的算法解决此问题。示例1:输入:nums=[100,4,200,1,3,2]输出:4解释:最长数字连续序列是[1,2,3,4]。......
  • 智慧城市总体规划方案:PPT全文43页,附下载​
    关键词:智慧城市建设方案,智慧城市规划,智慧城市发展前景与趋势,智慧城市管理平台,智慧城市建设,新型城镇化建设,城市数字化转型。一、传统模式城市的落后之处1、城市规划和设计传统城市往往因为过度成熟而陷入滞涨,老城区的房子楼龄过大,设计落后且没有优质学区。这导致其金融属性和居住属......
  • 【木棉花】向用户动态申请授权
    前言应用向用户动态申请授权,是指在用户使用应用的过程中,应用方会根据应用场景和业务向用户动态地请求相应的权限。例如,当应用需要访问用户的相机或麦克风时,会向用户弹出一个授权请求框,询问用户是否允许应用访问这些设备,而用户可以选择允许或拒......
  • 批量修改Fasta文件中序列的名称
    比如一个Fasta文件的内容如下:seq001|aaaATCGGGGseq002|bbbAAAATTTT删除序列名称中“|”后的内容,只保留seq001,seq002这样的名称点击查看代码#!/usr/bin/envpythonimportsysimportpysamwithpysam.FastxFile(sys.argv[1])asfh:forrinfh:new_n......
  • Prufer序列 学习笔记
    2023.10.29晚,在随机做AtCoder的时候见到了[ABC303Ex]ConstrainedTreeDegree。然后开始思考DP,未果后看题解,发现是Prufer序列->尝试学习Prufer序列。什么是Prufer序列Prufer序列是一种将带标号的树用一个唯一的整数序列表示的方法,是解决树计数问题的工具。给一棵有根树......
  • python vtk读取dicom序列+鼠标键盘交互
    目标:vtk+pyqt实现四视图。之前不了解vtk,也不了解鼠标键盘交互。网上搜索了资料,发现博客里大都是C++的例子。困扰几天,今天终于做出来一部分,分享一下。参考官方教程:examples.vtk.org/site/Python/IO/ReadDICOM/examples.vtk.org/site/Python/IO/ReadDICOMSeries/第一步:py......