算法竞赛进阶指南 0x50 总论
AcWing895. 最长上升子序列
方法一
采用从前往后推的方法
#include <bits/stdc++.h>
using namespace std;
#define N 1006
typedef long long ll;
ll a[N];
ll f[N];
int main()
{
int n;
scanf("%d", &n);
for(int i = 1; i <= n; i++) scanf("%lld", a+i);//注意:我存储的是ll,所以输入也应该是lld
f[0] = 0;//注意:初始条件一定要给
a[0] = INT_MIN;//注意:对于这种有对比的情况,是要更改一下原数组的第一个元素
for(int i = 0; i < n; i++)
for(int j = i+1; j <= n; j++)
if(a[j] > a[i])
{
f[j] = max(f[j], f[i]+1);
}
ll ans = 0;
for(int i = 1; i <= n; i++)//注意:这里的最优解并不是从最后一个元素里面找,而是在1~N中找
{
ans = max(ans, f[i]);
}
cout << ans;
return 0;
}
方法二
思考这一种状态可以由哪些推过来
#include <bits/stdc++.h>
using namespace std;
#define N 1006
typedef long long ll;
ll a[N];
ll f[N];
int main()
{
int n;
scanf("%d", &n);
for(int i = 1; i <= n; i++) scanf("%lld", a+i);
f[0] = 0;//注意:初始条件一定要给
a[0] = INT_MIN;//注意:对于这种有对比的情况,是要更改一下原数组的第一个元素
for(int i = 1; i <= n; i++)
for(int j = 0; j < i; j ++)
{
if(a[j] < a[i])
{
f[i] = max(f[i], f[j]+1);
}
}
ll ans = 0;
for(int i = 1; i <= n; i++)
{
ans = max(ans, f[i]);
}
cout << ans;
return 0;
}
当询问最长子序列是哪些的时候
#include <bits/stdc++.h>
using namespace std;
#define N 1006
typedef long long ll;
ll a[N];
ll f[N];
ll path[N];
void Print(int i)
{
if(i==0) return;
Print(path[i]);
cout << a[i] << ' ';
}
int main()
{
int n;
scanf("%d", &n);
for(int i = 1; i <= n; i++) scanf("%lld", a+i);
f[0] = 0;//注意:初始条件一定要给
a[0] = INT_MIN;//注意:对于这种有对比的情况,是要更改一下原数组的第一个元素
for(int i = 1; i <= n; i++)
for(int j = 0; j < i; j ++)
{
if(a[j] < a[i])
{
if(f[i] < f[j]+1)
{
path[i] = j;//并不需要把之前的全部存起来,只需要知道从哪里转移过来就行了
f[i] = f[j]+1;
}
}
}
ll ans = 0;
ll pos = 0;
for(int i = 1; i <= n; i++)
{
if(f[i] > ans)
{
ans = f[i];
pos = i;
}
}
Print(pos);
puts("");
cout << ans;
return 0;
}
896. 最长上升子序列 II
思路
这一道题目不能再使用动态规划来进行求解
必须采用更为快速的
O(NlogN)做法:贪心+二分
a[i]表示第i个数据。
dp[i]表示表示长度为i+1的LIS结尾元素的最小值。
利用贪心的思想,对于一个上升子序列,显然当前最后一个元素越小,越有利于添加新的元素,这样LIS长度自然更长。
因此,我们只需要维护dp数组,其表示的就是长度为i+1的LIS结尾元素的最小值,保证每一位都是最小值,这样子dp数组的长度就是LIS的长度。
dp数组具体维护过程同样举例讲解更为清晰。
同样对于序列 a(1, 7, 3, 5, 9, 4, 8),dp的变化过程如下:
- dp[0] = a[0] = 1,长度为1的LIS结尾元素的最小值自然没得挑,就是第一个数。 (dp = {1})
- 对于a[1]=7,a[1]>dp[0],因此直接添加到dp尾,dp[1]=a[1]。(dp = {1, 7})
- 对于a[2]=3,dp[0]< a[2]< dp[1],因此a[2]替换dp[1],令dp[1]=a[2],因为长度为2的LIS,结尾元素自然是3好过于7,因为越小这样有利于后续添加新元素。 (dp = {1, 3})
- 对于a[3]=5,a[3]>dp[1],因此直接添加到dp尾,dp[2]=a[3]。 (dp = {1, 3, 5})
- 对于a[4]=9,a[4]>dp[2],因此同样直接添加到dp尾,dp[3]=a[9]。 (dp = {1, 3, 5, 9})
- 对于a[5]=4,dp[1]< a[5]< dp[2],因此a[5]替换值为5的dp[2],因此长度为3的LIS,结尾元素为4会比5好,越小越好嘛。(dp = {1, 3, 4, 9})
- 对于a[6]=8,dp[2]< a[6]< dp[3],同理a[6]替换值为9的dp[3],道理你懂。 (dp = {1, 3, 5, 8})
这样子dp数组就维护完毕,所求LIS长度就是dp数组长度4。
通过上述求解,可以发现dp数组是单调递增的,因此对于每一个a[i],先判断是否可以直接插入到dp数组尾部,即比较其与dp数组的最大值即最后一位;如果不可以,则找出dp中第一个大于等于a[i]的位置,用a[i]替换之。
这个过程可以利用二分查找,因此查找时间复杂度为O(logN),所以总的时间复杂度为O(N*logN)转载自 紫芝 https://blog.csdn.net/qq_40507857/article/details/81198662 CSDN[1]
这道题目恰恰使用到了贪心的思想。
dp
数组里面存放了(当前当前位置之前,所有)长度为 i 的上升子序列的最小的末尾元素(只需要一个代表元,那就是结尾的元素)
依次从左向右进行扫描,对比 a[ i ] 和dp数组里面的值。
- 如果
a[i]
的值大于dp
数组的最后一个值,那么由于a[i]
的加入,就会让最长子序列长度增加。 - 如果
a[i]
的值小于等于dp
数组的最后一个元素,找一个大于等于a[i]
的第一个元素的下标p。
p之前的数字全部比 a 小,a 可以替换p的位置,完全合法,并且让p的位置的数字更小,更可能出现较长的上升子序列。
代码
#include <bits/stdc++.h>
using namespace std;
#define N 100020
int a[N], dp[N];
int m = 0;
int main()
{
int n;
scanf("%d", &n);
for(int i = 1; i <= n; i++) scanf("%d", a+i);
dp[1] = a[1];
m = 1;
for(int i = 2; i <= n; i++)
{
if(a[i] > dp[m])
{
dp[++m] = a[i];
continue;
}
int p = lower_bound(dp+1, dp+1+m, a[i])-(dp+1)+1;
dp[p] = a[i];
}
printf("%d", m);
return 0;
}
竟然更加简单:)
AcWing\897. 最长公共子序列
思路
闫氏DP分析法
状态:
dp[i][j]
代表在A序列里面以i
结尾,在B中j
结尾的序列。- 子结构:A中以
i
结尾,在B中j
结尾的序列中所有的公共子序列。 - 最优子结构:最长的公共子序列
- 状态表示:使用一个整数,表示长度。
- 子结构:A中以
转移:
对于dp[i][j]
一共有以下四种情况:包含
a[i]
,包含b[j]
;
注意有前提条件:a[i] == b[j]
这时候状态表示是dp[i-1][j-1]+1
包含
a[i]
,不包含b[j]
;
没有办法求得这种情况的等价情况。
但是有一个集合可以满足要求:dp[i][j-1]
这一个集合有两种情况- 包含
a[i]
,不包含b[j]
- 不包含
a[i]
,不包含b[j]
这是因为我的状态表示仅仅说明是
a[1~i]
和b[1~j]
的最长的公共子序列,包不包含a[i]
我并不知道。- 包含
不包含
a[i]
,包含b[j]
; 和上面一致!不包含
a[i]
,不包含b[j]
。 显然是dp[i-1][j-1]
由于有重复情况,所以最终dp方式见代码
代码
#include <bits/stdc++.h>
using namespace std;
#define N 1020
char a[N], b[N];
int dp[N][N];
int main()
{
int n, m;
scanf("%d%d", &n, &m);
scanf("%s", a+1);
scanf("%s", b+1);
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(a[i] == b[j])
dp[i][j] = max(dp[i-1][j-1]+1, dp[i][j]);
}
}
printf("%d", dp[n][m]);
return 0;
}
AcWing898. 数字三角形
输入样例:
5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
输出样例:
30
思路
这一道题目给定的三角不便于存储,要想办法改变一下。我们可以使用样例中的办法进行存储
这样,对应的规则就变成了向下或者向右下方走。
#include <bits/stdc++.h>
using namespace std;
#define N 506
int a[N][N];
int dp[N][N];
#define WAY2
//更换上面这一个定义,可以使用两种方法!
int main()
{
int n;
scanf("%d", &n);
for(int i = 1; i <= n; i++)
for(int j = 1; j <= i; j++)
dp[i][j] = -0x3f3f3f3f;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= i; j++)
scanf("%d", &a[i][j]);
#ifdef WAY1
dp[1][1] = a[1][1];
for(int i = 1; i < n; i++)
for(int j = 1; j <= i; j++)
{
dp[i+1][j] = max(dp[i+1][j], dp[i][j]+a[i+1][j]);
dp[i+1][j+1] = max(dp[i+1][j+1], dp[i][j]+a[i+1][j+1]);
}
#endif
#ifdef WAY2
dp[1][1] = a[1][1];
for (int i = 2; i <= n; i++)
for(int j = 1; j <= i; j++)
{
if(j > 1)dp[i][j] = max(dp[i][j], dp[i-1][j-1] + a[i][j]);//注意两个边界条件的判断
if(j< i ) dp[i][j] = max(dp[i][j], dp[i-1][j]+a[i][j]);
}
#endif
int ans = -0x3f3f3f3f;
for(int i = 1; i<= n; i++)
{
ans = max(ans, dp[n][i]);
}
printf("%d", ans);
return 0;
}
参考资料
算法竞赛进阶指南 0x50 总论的更多相关文章
- 《算法竞赛进阶指南》0x10 基本数据结构 Hash
Hash的基本知识 字符串hash算法将字符串看成p进制数字,再将结果mod q例如:abcabcdefg 将字母转换位数字(1231234567)=(1*p9+2*p8+3*p7+1*p6+2*p5 ...
- 《算法竞赛进阶指南》1.4Hash
137. 雪花雪花雪花 有N片雪花,每片雪花由六个角组成,每个角都有长度. 第i片雪花六个角的长度从某个角开始顺时针依次记为ai,1,ai,2,-,ai,6. 因为雪花的形状是封闭的环形,所以从任何一 ...
- bzoj 1787 && bzoj 1832: [Ahoi2008]Meet 紧急集合(倍增LCA)算法竞赛进阶指南
题目描述 原题连接 Y岛风景美丽宜人,气候温和,物产丰富. Y岛上有N个城市(编号\(1,2,-,N\)),有\(N-1\)条城市间的道路连接着它们. 每一条道路都连接某两个城市. 幸运的是,小可可通 ...
- POJ1639 算法竞赛进阶指南 野餐规划
题目描述 原题链接 一群小丑演员,以其出色的柔术表演,可以无限量的钻进同一辆汽车中,而闻名世界. 现在他们想要去公园玩耍,但是他们的经费非常紧缺. 他们将乘车前往公园,为了减少花费,他们决定选择一种合 ...
- 算法竞赛进阶指南 0x00 基本算法
放在原来这个地方不太方便,影响阅读体验.为了读者能更好的刷题,另起一篇随笔. 0x00 基本算法 0x01 位运算 [题目][64位整数乘法] 知识点:快速幂思想的灵活运用 [题目][最短Hamilt ...
- 算法竞赛进阶指南--快速幂,求a^b mod p
// 快速幂,求a^b mod p int power(int a, int b, int p) { int ans = 1; for (; b; b >>= 1) { if (b &am ...
- 算法竞赛进阶指南0x14 Hash
组成部分: 哈希函数: 链表 AcWing137. 雪花雪花雪花 因为所需要数据量过于大,所以只能以O(n)的复杂度. 所以不可能在实现的过程中一一顺时针逆时针进行比较,所以采用一种合适的数据结构. ...
- 《算法竞赛进阶指南》1.6Trie
142. 前缀统计 给定N个字符串S1,S2-SN,接下来进行M次询问,每次询问给定一个字符串T,求S1-SN中有多少个字符串是T的前缀. 输入字符串的总长度不超过106,仅包含小写字母. 输入格式 ...
- 《算法竞赛进阶指南》 1 (P4) a^b 快速幂
快速幂 #include<cstdio> #include<cmath> #include<iostream> using namespace std; long ...
随机推荐
- vue - Vue脚手架/消息订阅与发布
今天的内容有意思了,朋友们继续对我们之前的案例完善,是这样的我们之前是不是靠props来完成父给子,子给父之间传数据,其实父给子最好的方法就是props但是自给父就不是了,并且今天学下来,不仅如此,组 ...
- 什么叫做 SSO
什么叫做 SSO 本文写于 2020 年 12 月 8 日 SSO 的全称叫做 Single Sign On,意味「单点登录」. 何为单点登录?就是你希望自己的两个网站,可以做到:一个网站登录了,另一 ...
- 不可不知的 MySQL 升级利器及 5.7 升级到 8.0 的注意事项
数据库升级,是一项让人喜忧参半的工程.喜的是,通过升级,可以享受新版本带来的新特性及性能提升.忧的是,新版本可能与老的版本不兼容,不兼容主要体现在以下三方面: 语法不兼容. 语义不兼容.同一个SQL, ...
- 2. springboot加载配置参数顺序
加载顺序依次是:1.jar的classes里面的application.properties 2.当前路径下config里面的application.properties 3.jar的classes里 ...
- Ajax——Get请求
Get.html: <!DOCTYPE html> <html lang="en"> <head> <meta charset=" ...
- springBoot 定时+发送邮件
定时任务引入meaven依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifa ...
- CF1682D Circular Spanning Tree
题意: 构造题,节点1~n顺时针排列成圆形,告诉你每个点度数奇偶性,让你构造一棵树,树边不相交. 思路: 因为每条边给总度数贡献2,因此如果度数为1的点有奇数个,直接输出no.显然0个度数为1的,也输 ...
- 零成本搭建个人博客之图床和cdn加速
本文属于零成本搭建个人博客指南系列 为什么要使用图床 博客文章中的图片资源文件一般采用本地相对/绝对路径引用,或者使用图床通过外链进行引用展示.本地引用的弊端我认为在于: 图片和博客放在同一个代码托管 ...
- 抽象类与接口——JavaSE基础
抽象类与接口 抽象类 抽象类既包含规范又包含具体实现 抽象类可以包含实现的方法 和 未实现的用abstract修饰的抽象方法 抽象类不可以有实例化(不能使用new实例化),只能通过子类继承,然后对子类 ...
- Halodoc使用 Apache Hudi 构建 Lakehouse的关键经验
Halodoc 数据工程已经从传统的数据平台 1.0 发展到使用 LakeHouse 架构的现代数据平台 2.0 的改造.在我们之前的博客中,我们提到了我们如何在 Halodoc 实施 Lakehou ...