嘎嘎嘎,通过23日到28日某校某老师的生动讲解,让本蒟蒻更加理解了一些知识
day1:分治以及归并 分治:顾名思义:分而治之。
作为一个非常人(ě)性(xīn)的算法,度娘给出的解释如下:
把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。
感觉挺清晰易懂的,就不再赘述
举一个栗子
该题就是一道分治的典型例题,和循环比赛的解法类似,不做过多的讲解(看见题解里的大佬用位运算解决的我瑟瑟发抖)
不过还是要练习分治呀,毕竟又不是所有的题都能用位运算解决
归并:
归并就是将序列一直分下去,直到不能再分为止,即子序列的长度为1,然后再进行合并,把小的数放在前面,达到排序的效果(为什么不叫做分并算法,不是更形象吗)
贴图喽
由此可见,归并的时间复杂度仅为O(nlogn),十分地迅速,并且还是一个稳定排序
那么,归并有什么用呢?
栗子来喽
双倍经验是不是
此题仔细读后可知,不就是个逆序对吗(不屑)
其实我们知道,归并排序就是将两个有序的序列合并为一个有序的序列,并且前面一个序列的下标总是比后面一个序列的下标要小,所以只要我们比较到后一个序列的某个数小于前一个序列对应的那个数,则那个数一定都比前一个序列的剩下的元素都要小,于是就可以统计答案
不清晰,举栗子:
假设有两个有序序列 A = {1,2,7,8,11} 和 B = {3,4,5,9,10},当A序列中的7与B序列中的3进行比较时,可以看出7 > 3,所以A序列中的7以及它后面的所有的元素都可以与3组成逆序对
剩下的同理,不再解释,相信各位奆佬早就搞定了这一个算法
day2:二分查找算法 二分:就是把一个序列分成两个部分,之后再进行一些 *** 作
二分查找和二分答案不同,二分答案多半要写个check函数,但二分查找多半不用
再来栗子
这道题可以用lower_bound函数解决,调用方法如下
int x = lower_bound(a + 1, a + 1 + n, k) - a; //x储存的是在a数组的1-n以内的位置中第一个值大于等于k元素的下标。因为返回值是一个地址,所以还要减去数组的地址,即数组名
当然,lower_bound还是要掌握手写的方法,理解它的原理,这里不再讲解
还是贴个lower_bound手写代码吧
#includeusing namespace std; #define SF scanf #define PF printf int a[105]; int Lower_bound(int l, int r, int x) { while(l + 1 < r) { int mid = (l + r) >> 1; if(a[mid] >= x) r = mid; else l = mid; } return r; } int main() { int n; SF("%d", &n); for(int i = 1; i <= n; i++) { SF("%d", &a[i]); } int m; SF("%d", &m); PF("%d", Lower_bound(0, n + 1, m)); return 0; }
注:二分算法一定要有单调性
day3 + day4:二分答案算法 二分答案:前提:知道答案的范围并且有“最大值最小”或“最小值最大”之类的词眼
二分答案可以分为4个步骤进行:
1、分什么?
2、怎么分?
3、比什么?
4、怎么比?
先解决第一个步骤。这个步骤很简单,都是二分答案算法,不分答案分啥啊?
再解决第二个步骤。我们一般用二分法进行这个 *** 作。注意:while循环的条件以及mid的求法很重要,写错了就会T到飞起。有while循环如下的写法:
1、
while(l + 1 < r)
2、
while(l < r)
3、
while(l <= r)
mid的求法有如下的写法:
1、
mid = (l + r) >> 1;
2、
mid = (l + r + 1) >> 1;
一定要根据题目的意思再进行斟酌,因题而异
接着解决第三个和第四个步骤。我们在分答案时,大概率不能直接用mid和答案进行比较,通常会写一个check函数,得到mid在进行check *** 作之后的值再与答案进行比较
我爱栗子
这道题还是一样,先二分答案,按坐标从小到大排个序。主要是check函数该怎么写
我们知道,我们分的是答案,所以我们要求出在满足我们分出答案的条件下的最多可以放置的奶牛数量
check函数如下,详见注释:
int check(int x) {//x为分出来的答案 int cnt = a[1] + x;//因为第一头奶牛一定在a[1]点处,若要满足答案,则第二只奶牛的坐标至少要比 //a[1]多x,故为a[1] + x; int sum = 1;//sum为在满足答案的情况下最多可以放置的奶牛数 for(int i = 2; i <= n; i++) {//1-n做循环 if(a[i] >= cnt){//说明该奶牛可以在a[i]点处 sum++;//累计答案 cnt = a[i] + x;//理由同上 } } return sum;//返回答案 }
好了,接下来我们接着分析
如果我们的返回值sum小于等于题目中要求的奶牛数,因为题目中要求的是最长的距离,所以要把左区间 l 置为 mid,不能置为mid + 1,因为有可能 mid 就是答案,不能跳过mid。如果返回值sum大于题目中要求的奶牛数,那我们就可以放放心心地将右区间 r 置为 mid - 1
完整代码:
//早期猿人代码,不好看,将就看吧 #includeusing namespace std; int n,c,l,r,mid; int a[1000005]; bool check(int x){ int cut=a[1]+x; int sum=1; for(int i=2;i<=n;i++){ if(a[i]>=cut){ sum++; cut=a[i]+x; } } return sum>=c; } int main(){ cin>>n>>c; for(int i=1;i<=n;i++){ cin>>a[i]; } sort(a+1,a+1+n); l=0; r=a[n]-a[1]; while(l 好了,二分答案也算是一个比较重要的算法,写起来必须要得心应手啊
day5:队列所谓队列,就是类似于我们生活中排队买票一样,先到的先买,买完就走。所以队列就是一种先进先出的线性结构
数组模拟:
没有好吃的栗子了呜呜呜众所周知,队列有以下几种基本 *** 作可以用数组模拟实现:
push(x) —— 将x这个元素加进队列中
pop() —— 将队头元素删除
empty() —— 判断队列是否为空
clear() —— 清空队列
front() —— 输出的队头元素
相信这些基本 *** 作根本难不倒大家,只需要用双指针模拟就可以了,l 指向队头,r 指向队尾
还是贴个代码吧#includeSTL:using namespace std; #define SF scanf #define PF printf int a[20005], l = 1, r = 1;//双指针法 void push(int s){//将元素压入队列中 a[r++] = s; } void front(){//取出队头元素 if(l != r) {//判断是否为空 PF("%dn", a[l]); } else { PF("errorn"); } } void pop(){//删除队头元素 if(l != r) {//判断是否为空 l++; } else { PF("errorn"); } } void clear(){//清空队列 l = r; } void empty(){//判断是否为空 if(l == r) {//只要左指针与右指针相同就为空 PF("emptyn"); } else { PF("not emptyn"); } } int main() { int n; SF("%d", &n); for(int i = 1; i <= n; i++) { string x; cin >> x; if(x == "push") { int s; SF("%d", &s); push(s); } else if(x == "front") { front(); } else if(x == "pop") { pop(); } else if(x == "clear") { clear(); } else { empty(); } } return 0; } 首先我们需要头文件:include
定义:queue<类型> 名称;
我们一般用q作为队列的名称
函数调用:
q.push(x) —— 将x压入队列中
q.pop() —— 将队头元素删除
q.front() —— 输出队头元素
q.empty() —— 判断队列是否为空。若为空返回1,否则返回0
q.size() —— 获取队列的长度,即队列中元素的个数
队列作为很经典的STL,也是十分实用的东西。大家也可以尝试一下自己去模拟一下循环队列
补充 优先队列:定义:priority_queue
q; 学名:大根堆
人话版:q里面的元素自动按照从大到小排序
TA的兄弟:priority_queue
, greater > q; 注意:最后必须要打空格,不然会识别成右移运算符
学名:小根堆
人话版:q里面的元素自动按照从小到大排序
是不是更实用了哈哈哈
day6:栈所谓栈,与队列就只有一点不同:队列是先进先出,栈是先进后出,就像一个杯子一样
数组模拟:
栗子被吃完了呜呜呜用数组模拟的栈的 *** 作与上面的队列一样,这里只说方法
队列需要双指针模拟,栈不一样,栈只需要单指针 r 充当右指针就行了
代码:
#includeSTL:using namespace std; #define SF scanf #define PF printf int a[20005], r = 0;//r充当栈的右指针 void push(int s) {//将s压入栈中 a[r++] = s; } bool empty() {//判断栈是否为空 if(r == 0) return true; else return false; } void pop() {//删除栈顶元素 r--; } void clear() {//将栈清空 r = 0; } void top() {//输出栈顶元素 PF("%dn", a[r - 1]); } int main() { int n; SF("%d", &n); for(int i = 1; i <= n; i++) { string x; cin >> x; if(x == "push") { int s; SF("%d", &s); push(s); } else if(x == "top") { if(!empty()) top(); else PF("errorn"); } else if(x == "pop") { if(!empty()) pop(); else PF("errorn"); } else if(x == "empty") { if(!empty()) PF("not emptyn"); else PF("emptyn"); } else { clear(); } } return 0; } 头文件:include
定义:stack<类型> 名称;
我们一般用s作为栈的名称
函数调用:
s.push(x) —— 将x压入栈中
s.pop() —— 将栈顶元素删除
s.front() —— 输出栈顶元素
s.empty() —— 判断栈是否为空。若为空返回1,否则返回0
s.size() —— 获取栈的长度,即栈中元素的个数
栈和队列一样,都是STL中很实用的东西,也是很简单的,但是也非常重要。要学会bfs搜索,就必须要学会队列。栈也是计算机中储存数据的基本线性结构,所以必须要掌握呀!
撒花~~~~~完结了 really? YES!欢迎分享,转载请注明来源:内存溢出
评论列表(0条)