算法竞赛进阶指南 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 ...
随机推荐
- Java 17 新特性:switch的模式匹配(Preview)
还记得Java 16中的instanceof增强吗? 通过下面这个例子再回忆一下: Map<String, Object> data = new HashMap<>(); da ...
- VMware 虚拟机图文安装和配置 AlmaLinux OS 8.6 教程
前言: 这是<VMware 虚拟机图文安装和配置 Rocky Linux 8.5 教程>一文的姐妹篇教程,如果你需要阅读它,请点击这里. 2020 年,CentOS 宣布:计划未来将重心从 ...
- 四、针对redis容灾切换导致"脑裂"的情况
网上参考到别人博客说,redis容灾切换的时候,有几率出现脑裂的情况. 什么是脑裂: sentinel判断master宕机,切换slave为新master的过程中,业务数据还在持续往原master写入 ...
- Spring Security之用户名+密码登录
自定义用户认证逻辑 处理用户信息获取逻辑 实现UserDetailsService接口 @Service public class MyUserDetailsService implements Us ...
- awk-文本处理【中文手册版】
01. 简介 AWK是一个文本(面向行和列)处理工具,同时它也是一门脚本语言. AWK其名称得自于它的创始人 Alfred Aho .Peter Weinberger 和 Brian Kernigha ...
- ML第2周学习小结
本周收获 总结一下本周学习内容: 1.复习了Numpy的一些基础操作,主要是利用numpy来对ndarray数组进行操作 我的博客链接: Numpy的一些操作 2.正在学习<深入浅出Pandas ...
- 博弈论(nim游戏,SG函数)
说到自己,就是个笑话.思考问题从不清晰,sg函数的问题证明方法就在眼前可却要弃掉.不过自己理解的也并不透彻,做题也不太行.耳边时不时会想起alf的:"行不行!" 基本的小概念 这里 ...
- 「ARC 139F」Many Xor Optimization Problems【线性做法,踩标】
「ARC 139F」Many Xor Optimization Problems 对于一个长为 \(n\) 的序列 \(a\),我们记 \(f(a)\) 表示从 \(a\) 中选取若干数,可以得到的最 ...
- Linux挂载iso镜像、配置本地yum源
Linux挂载iso镜像.配置本地yum源 1.备份原yum源配置文件 [root@localhost ~]# ll /etc/yum.repos.d/ [root@localhost ~]# mkd ...
- BUUCTF-荷兰宽带数据泄露
荷兰宽带数据泄露 下载后发现是个BIN文件,之前也是做过类似的题目 RouterPassview打开BIn文件即可,搜索username或者password. 最后flag是username