学大伟业 Day 5 培训总结
今天讲数据结构
先从mzx大佬的ppt摘抄一段:
数据结构是计算机存储、组织数据的方式。数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。
通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率。数据结构往往同高效的检索算法和索引技术有关。(摘自百度百科)
简单来说,我们可以把数据的集合视为一个数组,即数组的元素就是我们所存储的数据,
同时,我们再用一系列指针(在OI中一般不用指针而用下标来代替)维护这些数据之间的关系,使得我们能够更方便地管理我们所需要用到的数据。
数据结构很多时候是用来优化算法的,所以我们需要搞清楚我们算法需要对数据进行什么样的操作,根据需求来选择所需要的数据结构。
在学习一个数据结构时,你应该按这样的先后顺序来记忆: 1.作用(支持的操作)及复杂度 2.原理(代码实现) 3.正确性/复杂度证明
数据结构存储的是数据,它们可以是整数(int/long long),实数(float/double),也可以是我们自己定义的结构体。
目录:
栈
队列
堆(优先队列)
分块
线段树
树状数组
一.栈
栈是一种先进后出(FILO,即first-in-last-out)的数据结构,你可以把它想象成一个只开一个口的乒乓球筒,每次只能从最顶端处放置或取出数据。
一般在OI中我们用一个数组和栈顶指针(用int表示的下标)来模拟一个栈。
栈支持两个操作:1.向栈顶加入一个元素 2.从栈顶取出一个元素 这两个操作的时间复杂度都是O(1)。
那么它所维护的这个数据集合中,数据之间有什么样的关系呢?
很简单,就是fisrt-in-last-out,或者更具体一点:如果一个元素在另一个元素之前加入这个集合中,那么它一定在那个元素之后退出这个集合。(摘自mzx大佬的ppt)
看一道例题:
火车进站
有一个车站,每天都会有N辆车进站,进站按从1到N的顺序进站。
现在车站的站长想让这些火车按照特定的顺序出站,问可以做到吗?
当N为5时,出站顺序若为1 2 3 4 5,可以做到,但是顺序若为5 4 1 2 3,则不行。
直接用栈模拟:
1 #include<iostream> 2 #include<cstdio> 3 using namespace std; 4 int n,a[101],s[101],now,top; 5 int main() 6 { 7 scanf("%d",&n); 8 for(int i=1;i<=n;i++) 9 scanf("%d",&a[i]); 10 now=1; 11 for(int i=1;i<=n;i++) 12 { 13 top++; 14 s[top]=i; 15 while((s[top]==a[now])&&(top>0)&&(now>0)) 16 { 17 top--; 18 now++; 19 } 20 } 21 if(top!=0) 22 printf("NO"); 23 else 24 printf("YES"); 25 return 0; 26 }
值得注意的是此题时间复杂度需要用均摊分析:
第二层循环一共执行n次,最坏一次n次,均摊后复杂度为O(1),总复杂度为O(n)。by——cym大佬的笔记(其实还是mzx讲的)
单调栈:
单调栈只是栈的一个特殊用法而已,本质上依然是一个栈。
单调栈要求栈中的元素从栈底到栈顶是单调(有序)的,所以在加入一个新的元素时,如果它和栈顶元素不满足单调,那么将会依次弹出栈顶的元素直到这个元素加入到栈中依然满足单调,才
将这个元素加入到栈中。
队列:
队列是一种先进先出(FIFO,即first-in-fisrt-out)的数据结构,你可以把它想象成一个羽毛球筒,每次只能从尾部添加元素,从头部取出元素,除非你想把你的羽毛球弄坏。
一般在OI中我们用一个数组和队头和队尾指针(用int表示的下标)来模拟一个队列。
队列支持两个操作:1.向队尾加入一个元素 2.从队头取出一个元素 这两个操作的时间复杂度都是O(1)。
那么它所维护的这个数据集合中,数据之间有什么样的关系呢? 很简单,就是fisrt-in-first-out,或者更具体一点:如果一个元素在另一个元素之前加入这个集合中,那么它一定在那个元素之前
退出这个集合。
上面两个没例题是因为我懒...有时间会补的(胡说八道)
其实我更关心的是这个:
单调队列!
单调队列只是队列的一个特殊用法而已,本质上依然是一个队列。
单调队列要求队列中的元素从队头到队尾是单调(有序)的,所以在加入一个新的元素时,如果它和队尾元素不满足单调,那么将会依次弹出栈顶的元素直到这个元素加入到队尾依然满足单
调,才将这个元素加入到队列中。
需要注意的是,这个插入到队尾的操作和单调栈比较相似,甚至可以说一样,不同的是取元素时的位置。
例题:
首先想到的就应该是:滑动窗口!
题目链接:https://www.luogu.org/problemnew/show/P1886
这道题是一道典型的单调队列
在这里引用luogu一位大佬hankeke对单调队列的讲解:
单调队列有两个性质
队列中的元素其对应在原来的列表中的顺序必须是单调递增的。
- 队列中元素的大小必须是单调递*(增/减/甚至是自定义也可以)
单调队列与普通队列不一样的地方就在于单调队列既可以从队首出队,也可以从队尾出队。
那么我们应该怎样实现单调队列呢?
就拿样例来谈谈,设以最小的为标准。
8 3
1 3 -1 -3 5 3 6 7
下文中我们用q来表示单调队列,p来表示其所对应的在原列表里的序号。
由于此时队中没有一个元素,我们直接令1进队。此时,q={1},p={1}。
现在3面临着抉择。下面基于这样一个思想:假如把3放进去,如果后面2个数都比它大,那么3在其有生之年就有可能成为最小的。此时,q={1,3},p={1,2}
下面出现了-1。队尾元素3比-1大,那么意味着只要-1进队,那么3在其有生之年必定成为不了最小值,原因很明显:因为当下面3被框起来,那么-1也一定被框起来,所以3永远不能当最小值。所以,3从队尾出队。同理,1从队尾出队。最后-1进队,此时q={-1},p={3}
出现-3,同上面分析,-1>-3,-1从队尾出队,-3从队尾进队。q={-3},p={4}。
出现5,因为5>-3,同第二条分析,5在有生之年还是有希望的,所以5进队。此时,q={-3,5},p={4,5}
出现3。3先与队尾的5比较,3<5,按照第3条的分析,5从队尾出队。3再与-3比较,同第二条分析,3进队。此时,q={-3,3},p={4,6}
出现6。6与3比较,因为3<6,所以3不必出队。由于3以前元素都<3,所以不必再比较,6进队。因为-3此时已经在滑动窗口之外,所以-3从队首出队。此时,q={3,6},p={6,7}
- 出现7。队尾元素6小于7,7进队。此时,q={3,6,7},p={6,7,8}。
那么,我们对单调队列的基本操作已经分析完毕。因为单调队列中元素大小单调递*(增/减/自定义比较),因此,队首元素必定是最值。按题意输出即可。
代码如下:
1 #include <iostream> 2 #include <cstdio> 3 using namespace std; 4 const int maxn = 2000000 + 10; 5 int a[maxn], que[maxn], ppp[maxn], head, tail, k, n; 6 int min_queue() 7 { 8 head = 1; tail = 0; 9 for(int i = 1; i <= n-1; i++) 10 { 11 12 while(head<=tail && que[tail]>=a[i]) tail--; 13 que[++tail] = a[i]; 14 ppp[tail] = i; 15 while(ppp[head]<=i-k) 16 head++; 17 if(i>=k) printf("%d\n",que[head]); 18 } 19 printf("\n"); 20 } 21 int max_queue() 22 { 23 head = 1; tail = 0; 24 for(int i = 1; i <= n; i++) 25 { 26 while(head<=tail && que[tail]<=a[i]) tail--; 27 que[++tail] = a[i]; 28 ppp[tail] = i; 29 while(ppp[head]<=i-k) 30 head++; 31 if(i>=k) printf("%d ",que[head]); 32 } 33 } 34 int main() 35 { 36 scanf("%d%d", &n, &k); 37 for(int i = 1; i <= n; i++) 38 scanf("%d",&a[i]); 39 //printf("0\n"); 40 min_queue(); 41 max_queue(); 42 return 0; 43 }
还有一道是luogu的P1440
这道题看到是让查询区间的最值,果断想到用ST表头铁一波,然后T了两个点(当然有的大佬会用ST表优化过掉...然而我不会...)
1 //ST表头铁80分 2 #include <cstdio> 3 #include <algorithm> 4 using namespace std; 5 const int maxn = 2000000 + 1; 6 int st[maxn][21], ans[maxn]; 7 int j, i, left, right, n, m, a; 8 int main() 9 { 10 scanf("%d%d",&n, &m); 11 for(i = 1; i <= n; i++) 12 { 13 scanf("%d",&a); 14 st[i][0] = a; 15 } 16 17 for(j = 1; (1<<j) <= n; j++) 18 for(i = 1; i <= n-(1<<j)+1; i++) 19 st[i][j] = min(st[i][j-1],st[i+(1<<(j-1))][j-1]); 20 21 for(i = 1; i <= n-m+1; i++) 22 { 23 j = 0; 24 left = i; 25 right = left + m - 1; 26 27 while((1<<(j+1)) <= (right-left+1)) j++; 28 ans[i] = min(st[left][j],st[right-(1<<j)+1][j]); 29 30 } 31 printf("0\n"); 32 for(int i=1;i<=m-1;i++) 33 { 34 left = 1; 35 right = i; 36 j = 0; 37 while((1<<(j+1)) <= (right-left+1)) j++; 38 printf("%d\n",min(st[left][j],st[right-(1<<j)+1][j])); 39 } 40 for(i = 1; i <= n-m; i++) 41 printf("%d\n",ans[i]); 42 } 43 //前m-1个数 ,第一个一定是0
当然正解就是我们要用到的单调队列啦
其实把滑动窗口的改一改就好...
1 #include <iostream> 2 #include <cstdio> 3 using namespace std; 4 const int maxn = 2000000 + 10; 5 int a[maxn], que[maxn], ppp[maxn], head, tail, k, n; 6 int min_queue() 7 { 8 head = 1; tail = 0; 9 for(int i = 1; i <= n-1; i++) 10 { 11 12 while(head<=tail && que[tail]>=a[i]) tail--; 13 que[++tail] = a[i]; 14 ppp[tail] = i; 15 while(ppp[head]<=i-k) 16 head++; 17 /*if(i>=k)*/ printf("%d\n",que[head]); 18 } 19 printf("\n"); 20 } 21 /*int max_queue() 22 { 23 head = 1; tail = 0; 24 for(int i = 1; i <= n; i++) 25 { 26 while(head<=tail && que[tail]<=a[i]) tail--; 27 que[++tail] = a[i]; 28 ppp[tail] = i; 29 while(ppp[head]<=i-k) 30 head++; 31 if(i>=k) printf("%d ",que[head]); 32 } 33 }*/ 34 int main() 35 { 36 scanf("%d%d", &n, &k); 37 for(int i = 1; i <= n; i++) 38 scanf("%d",&a[i]); 39 printf("0\n"); 40 min_queue(); 41 max_queue(); 42 return 0; 43 }
优先队列
“优先队列”这个概念包括了很多数据结构,我们把具有以下功能的数据结构统称为“优先队列”,或者说这些数据结构是“优先队列”的不同实现方式:
1.将一个元素插入到集合中
2.取出集合中最小/最大的元素 支持以上操作的数据结构有二叉堆/斜堆/二项堆/斐波那契堆等,这两个操作的时间复杂度一般为O(logn)(n为集合大小),当然,它们同时也支持很多其他操作,
比如修改元素的大小或者合并两个堆(支持这个操作的堆也叫可并堆)等,通过名字可以看到,优先队列一般也被称为“堆”。
但是,对于C++选手来说,stl内置了一个叫priority_queue(翻译即为优先队列)的数据结构,它支持插入,查询或删除最大值的操作,所以,我们一般只需要直接使用它而不需要自己手动实现
一个二叉堆(除非你需要支持其他的操作)。
在本博客里有STL堆模板了,这里不再放。
例题:合并果子
题目链接:https://www.luogu.org/problemnew/show/P1090#sub
思路:每次取出最小的两堆,合并完再入堆,直到堆里面没有元素。
转载于:https://www.cnblogs.com/MisakaAzusa/p/8477089.html
学大伟业 Day 5 培训总结相关推荐
- 学大伟业 Day 1 培训总结
第一天培训,讲的基本算法,东西很多.还有些数论,图论,数据结构and some small tricks 一.输入输出技巧 1 //输入输出技巧 2 /* 3 scanf.printf:速度快,需要记 ...
- 学大伟业 Day 6 培训总结
今天接着昨天的继续讲数据结构 今天先是 分块 在统计问题中,尤其是序列问题,经常涉及到区间的操作,比如修改一段区间的元素,询问某个区间的元素的信息. 如果每次都对一整个区间的每一个元素进行操作的话,那 ...
- 学大伟业 Day 3 培训总结
今天讲的字符串: 不多说,直接看题 一.表达式求值 题目大意: 输入一行一个表达式,计算其答案 表达式包含非负整数.加减乘除.括号 两种做法 ·栈 ·表达式树 这里更推荐表达式树,因为栈是先压进去,逆 ...
- 学大伟业 Day 2 培训总结
一.dp 动态规划的本质 是一种思想.通过对原问题划分成子问题,寻找子问题之间的联系,通过求解子问题得出原问题的解.与贪心不同的是,动归是深谋远虑,考虑全局最优解:而贪心则目光短浅,只考虑局部最优解. ...
- 学大伟业 Day 4 培训总结
今天讲的全是dp... 不多废话,先看一道经典的模板LIS(最长不下降子序列) 一.LIS 给定一个长度为N的数列,求最长上升子序列 例:1 7 2 8 3 4 答案:1 2 3 4 代码: 1 #i ...
- 学大伟业:学长是如何对待数学竞赛的
如果你是下定决心要学数竞,真的很认真地做了决定,那么你的自学能力必须要过关,专注度一定要够强.当然,这里的专注度不是指40分钟过去了,才连一条几何辅助线,也不是说60分钟过去了,你才完成了一试的填空题 ...
- 学大伟业:在数学竞赛学习中,你属于哪种类型?
学习数学竞赛,在其他人眼中是一种什么存在,你知道么? 你在学习数学竞赛中属于什么类型的,你自己知道么? 下面几种类型, 赶紧看看自己,中枪了没. 做题狂魔型 这类人的主要特征就是喜欢做题,简直到了一种 ...
- 学大伟业:如何利用课余时间学习物理竞赛,搞定自主招生?
今天撇开能力超强的学生不谈,仅针对目标自主招生的学生,谨慎的给出一些物理竞赛的学习建议. 高一 高中物理竞赛中力学.电磁学模块占据了70%的考试内容,这两个模块也是最难的模块,热学.光学.近代物理内容 ...
- 学大伟业Day1解题报告
学大伟业Day1解题报告 张炳琪 一. 时间分配 T1:30分钟 T2: 60分钟 T3:100分钟 二.答题情况及错因 T1:100 T2:55 T3 ...
最新文章
- 重新认识笔记本锂电池的保养
- webstorm2018破解方法
- 港科大陈凯、杨强教授新书重磅发布,系统揭秘隐私计算 | 文末送书
- SpringBoot启动流程是怎样的
- P3515-[POI2011]Lightning Conductor【整体二分,决策单调性】
- TortoiseGit与github实现项目的上传
- Tableau可视化学习笔记:day05-06
- scala 访问修饰符_Scala访问修饰符–私有,受保护的和公共的
- 关于360笔试部分题目小结
- xp怎么查看计算机配置,怎么查看xp系统的配置信息
- 论文发表的等级分为哪些
- 51ditu Maps API 类参考文档
- java shopnc,ShopNC商城系统
- html和css基础知识recap(含大量链接参考)
- css沿曲线进行动画,jQuery沿贝兹曲线运动动画特效
- iPad上的数学软件介绍与畅想
- win11任务栏右键没有任务管理器的解决办法 - 添加win11任务栏右键功能
- 读1973年的弹子球有感。_北国的雪_新浪博客
- 阴历五月一个下着雨夹雪的黄昏
- 2007年的有潜力的绩优股
热门文章
- 只有懦夫才会畏惧选择!
- 用VS2015开发Linux程序详细教程-配置篇
- kali linux安装upupoo_Kali Linux 下载、引导、安装
- 让div占据父元素剩下的所有位置
- linux使用windows无线网卡,linux下安装windows xp无线网卡驱动
- Nature Aging | 复旦大学冯建峰/程炜揭示每晚睡7小时刚刚好,睡过多或过少都有损大脑和心理健康...
- linux 信号signum.h SIGINT SIGUSR1 SIGUSR2
- UG NX 12装配——组件位置:装配约束
- Vue项目实现点击图标拨打电话
- 技术太多学不过来?教你如何越学越带劲