动态规划

P1439 【模板】最长公共子序列 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

题目描述

给出 1,2,…,n 的两个排列 P1 和 P2 ,求它们的最长公共子序列。

输入格式

第一行是一个数 n

接下来两行,每行为 n 个数,为自然数 1,2,…,n 的一个排列。

输出格式

一个数,即最长公共子序列的长度。

输入输出样例

输入 #1

5
3 2 1 4 5
1 2 3 4 5

输出 #1

3

说明/提示

  • 对于 50% 的数据, n≤1000;
  • 对于 100% 的数据, n≤100000。

首先 我们区分两个概念:

  • 子序列:序列的一部分项按原有次序排列而得的序列,也是说 这里 如 3 1 5 也算子序列
  • 子串:串的连续一部分
  • 排列:从1~n不重复出现

这题如何用dp来解决?首先,我们把序列分为X和Y两个序列。

我们尝试寻找一个最优子结构和定义一个状态来表示公共子序列的大小。

于是,我们可以想到:从两个串的第一位开始逐个对比到至最后。我们定义状态d( i , j ),表示两个串 X 对比到第 i 位, Y 对比到第 j 位这个状态下的最长公共子序列,那么d(n,n)即为原题目的解。

我们可以写出状态转移方程:

由此,我们可以写出代码:

#include<stdio.h>
const int N = 1e3 + 7; //1007
int x[N];
int y[N];
int d[N][N];
int max(int x, int y) { return x > y ? x : y; }
int main()
{
int n;
scanf("%d", &n);
for (int i = 1; i <= n; i++)
scanf("%d", &x[i]);
for (int i = 1; i <= n; i++)
scanf("%d", &y[i]);
for (int i = 1; i <= n; i++)
d[i][0] = d[0][i] = 0;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
{
if (x[i] == y[j])
d[i][j] = d[i - 1][j - 1] + 1;
else d[i][j] = max(d[i - 1][j], d[i][j - 1]);
}
printf("%d", d[n][n]);
return 0;
}

我们尝试对空间进行优化。

会发现,每次的d(I,j)都是由它左边d(I,j-1)或者上边d(i-1,j)或者左上的值转变而来。

那么我们是不是就可以用一个二维数组来滚动代替储存呢?

这样,我们就把第一维的空间压缩为2

由于我们循环执行的次数是n^2,所以时间复杂度是O(1e10),必定超时啊。

LCS&LIS问题

如果你能想出朴素的dp算法那你在第一层,能够用滚动数组优化空间那你在第二层,然而出题人在第五层,算法竞赛就是这样。

我们发现两个数组都是全排列数组,也就是说a中的数字在b中只会出现一次,a中没出现的数字b中也不会出现,b只是a的另一种排列顺序。

那么我们以a的顺序为基准按出现的时机进行记录后再对b中的数字按照记录标记那么b中只有出现时机单调递增的子序列是符合题目的,这就让题目从LCS问题变为了LIS问题。

LCS问题:最长公共子序列问题

LIS问题: 最长上升子序列问题

​ ind[num] = i; data[i]=ind[num];

举个例子 求 1 7 6 2 3 4最长上升子序列

定义状态:d(i)表示以第i个数字结尾的最长上升子序列

状态转移:

初始状态:

对于每一个数来说,最长上升序列就是本身,即d [ i ] 的初始值为1

#include<stdio.h>
int a[100];
int dp[100];
int max(int x, int y) { return x > y ? x : y; }
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<=n;i++)
{
dp[i]=1;
for(int j=1;j<i;j++)
{
if(a[j]<a[i])
dp=max(dp[i],dp[j]+1);
}
printf("dp[%d]=%d",i,dp[i]);
}
return 0;
}

num 3 2 1 4 5 1 2 3 4 5

ind[3]=1,ind[2]=2,ind[1]=3,ind[4]=4,ind[5]=5

data[1]=ind[1]=3,data[2]=ind[2]=2,data[3]=ind[3]=1,data[4]=ind[4]=4,data[5]=ind[5]=5

dp[1]=1 i=2 j=1 data[1]>data[2],dp[2]=1 i=3 j=1 dp[3]=1 j=2 dp[3]=1

i=4 j=1 dp[4]=max(dp[4],dp[1]+1)=2 j=2 dp[4]=2

i=5 j=1 dp[5]=3

ans=3

但是 时间还是n^2

1 7 6 2 3 4

为了优化,我们可以另外开一个单调的数组,用于储存上升的数。

设置一个答案序列B,初始为空。第一次搜索到了1,将1加入答案序列,然后到了7,7>1故加入序列,随后到

了数字6,我们找到序列中第一个大于该数字的数,用该数字进行替换。

这是因为我们只需要求出长度,这样子替换不会对最终的答案造成影响可以视为答案序列被替换后的6即表示原序列6的位置,也表示7的位置。

最终答案序列是{1,2(6,7),3,4}

假如是5 2 3 1 4,最终答案序列是{1(2,5),3,4} 可以看出1的位置能够表示2或者5,最终序列的答案也是2,3,4。

由于该队列的严格单调,所以我们使用二分的方法去查找。

最后的答案即队列的长度。

1,7,6,2,3,4

i=1 {1}

i=2 {1,7}

i=3 {1,6(7)} 因为6比7小,覆盖了7可以使来了更大的数可以延长这个序列

i=4 {1,2(6,7)} 同理,其作用在下一行表现出来了

i=5 {1,2(6,7),3}

i=6 {1,2(6,7),3,4}

答案为4。

#include<stdio.h>
const int N = 1e5 + 7;
int data[N], dp[N], ind[N];
int goal[N]; //队列
int num, ans;
int max(int x, int y) { return x > y ? x : y; }
//二分查找
int search(int l, int r, int num)
{
while (l < r)
{
int mid = (l + r) >> 1;
if (num <= goal[mid]) r = mid;
else l = mid + 1;
}
return l;
}
int main()
{
int n;
scanf("%d", &n);
for (int i = 1; i <= n; i++)
{
scanf("%d", &num);
ind[num] = i;
}
for (int i = 1; i <= n; i++)
{
scanf("%d", &num);
data[i] = ind[num];
}
int len = 1;
goal[1] = data[1];
int pos = 0;
for (int i = 2; i <= n; i++)
{
//data[i]>队尾元素故加入序列
if (goal[len] < data[i])
goal[++len] = data[i];
//查找到第一个大于data[i]的数,用该数字进行替换
else
goal[search(1, len, data[i])] = data[i];
}
printf("%d", len);
return 0;
}

这样我们的时间可以化为O(n*log n)

【ACM程序设计】动态规划 第二篇 LCS&LIS问题的更多相关文章

  1. 华南师大 2017 年 ACM 程序设计竞赛新生初赛题解

    题解 被你们虐了千百遍的题目和 OJ 也很累的,也想要休息,所以你们别想了,行行好放过它们,我们来看题解吧... A. 诡异的计数法 Description cgy 太喜欢质数了以至于他计数也需要用质 ...

  2. 深入理解javascript对象系列第二篇——属性操作

    × 目录 [1]查询 [2]设置 [3]删除[4]继承 前面的话 对于对象来说,属性操作是绕不开的话题.类似于“增删改查”的基本操作,属性操作分为属性查询.属性设置.属性删除,还包括属性继承.本文是对 ...

  3. 深入理解javascript函数系列第二篇——函数参数

    × 目录 [1]arguments [2]内部属性 [3]函数重载[4]参数传递 前面的话 javascript函数的参数与大多数其他语言的函数的参数有所不同.函数不介意传递进来多少个参数,也不在乎传 ...

  4. 20145213《Java程序设计》第二周学习总结

    20145213<Java程序设计>第二周学习总结 教材学习内容总结 本周娄老师给的任务是学习教材的第三章--基础语法.其实我觉得还蛮轻松的,因为在翻开厚重的书本,一股熟悉的气息扑面而来, ...

  5. 20145337 《Java程序设计》第二周学习总结

    20145337 <Java程序设计>第二周学习总结 教材学习内容总结 Java可分基本类型与类类型: 基本类型分整数(short.int.long).字节(byte).浮点数(float ...

  6. 《Java程序设计》第二周学习总结

    20145224陈颢文<Java程序设计>第二周学习总结 教材学习内容总结 一.类型.变量与运算符 1.类型 整数: 可细分为为short整数(占2字节),int整数(占4字节),long ...

  7. 西南科技大学第十一届ACM程序设计大赛发言稿

    西南科技大学第十一届ACM程序设计大赛发言稿 各位老师.志愿者及参赛选手: 大家好,我是来自计科学院卓软1301的哈特13,很荣幸今天能站在这里代表参赛选手发言. 回想起来,我参加ACM比赛已经快两年 ...

  8. 记第五届山东省ACM程序设计比赛——遗憾并非遗憾

    记第五届山东省ACM程序设计比赛 5月10日上午9点半左右,我们的队伍从学校出发,一个多小时后到达本次比赛的地点-哈尔滨工业大学. 报道,领材料,吃午饭,在哈工大的校园里逛了逛,去主楼的自习室歇息了一 ...

  9. 20155304田宜楠 2006-2007-2 《Java程序设计》第二周学习总结

    20155304田宜楠 2006-2007-2 <Java程序设计>第二周学习总结 教材学习内容总结 一.类型与变量 1.类型 整数: 可细分为为short整数(占2字节),int整数(占 ...

随机推荐

  1. 微信小程序从注册到上线系列

    为了帮助同学们了解注册及上线的整个流程,所以在开发之外,我专门制作了这个从注册到上线流程:本专辑不涉及任何跟开发有关的事情,开发专辑请看:实战开发宝典 以下为具体内容: 从注册到上线系列<一&g ...

  2. 前端面试题整理——VUE双向绑定原理

    VUE2.0和3.0数据双向绑定的原理,并说出其区别. 代码: <!DOCTYPE html> <html lang="en"> <head> ...

  3. web开发者踏入人工智能的利器_Tensorflow.js

    前言 最近公司向员工搜集公司杂志的文章,刚好最近学习了机器学习相关课程.为了赚取购买课程的费用,所以写了如下文章投稿赚取稿费. 如下文章可能涉及一些我所购买课程的内容,所以不便将所有资源进行展示. 当 ...

  4. java中当static块和构造函数同时出现,顺序是?

    静态块先于构造函数执行 class Student {    int age;    String name;    static int count;    public Student() {   ...

  5. github账号&文章选题

    ----------------------------------------------------------- https://github.com/yanpanjiao     github ...

  6. vue行内动态添加样式或者动态添加类名

    还是记录一下吧(๑•ᴗ•๑) <li :style="{backgroundImage:`url(${item.pic})`}" @click="chooseVip ...

  7. 拼写检查-c++

    [问题描述] 作为一个新的拼写检查程序开发团队的成员,您将编写一个模块,用已知的所有形式正确的词典来检查给定单词的正确性.        如果字典中没有这个词,那么可以用下列操作中的一个来替换正确的单 ...

  8. smdms超市订单管理系统之登录功能

    一.超市订单管理系统准备阶段 Supermarket order management system 创建数据库 数据库代码放置如下 点击查看数据库address代码 CREATE TABLE `sm ...

  9. string 函数

    传送门:https://www.w3school.com.cn/php/php_ref_array.asp addcslashes() 返回在指定的字符前添加反斜杠的字符串. addslashes() ...

  10. python中faker模块的使用

    Faker 安装 pip install Faker 基本使用 from faker import Faker #创建对象,默认生成的数据为为英文,使用zh_CN指定为中文 fake = Faker( ...