2017清北学堂(提升组精英班)集训笔记——基础算法

我这更新笔记的顺序有点乱时间也很乱,见谅,(其实我是想偷懒什么简单先更什么O(∩_∩)O~)html

1、倍增算法:ios

定义:用f[i][j]表示从i位置出发的2j个位置的信息综合(状态)算法

一个小小的问题:为何是2j而不是3j,5j,…?由于,假设为kj,整个算法的时间复杂度为(k-1)logk,当k=2时,时间复杂度最小。数组

这个算法的三个应用:数据结构

1.倍增ST表:函数

应用:这个ST表是用来解决RMQ问题(给你n个数,m次询问,每次询问[l,r]这个区间的最大值),固然,解决RMQ问题是能够用线段树来作的,可是比较麻烦,NOIP 80%是不会用线段树来作,仍是用ST表方便。优化

定义:f[i][j]表示:从i到i+2j-1这2j个位置的元素最大值网站

初始化:f[i][0]=z[i](第i个位置到第i+20-1个位置的最大值,对应只有一个元素的区间)spa

转移:f[i][j]=max(f[i][j-1],f[i+2(j-1)][j-1]) (把[i,i+2j-1]这个区间分治为两个区间,这两个区间拼在一块儿就成了原来一个大的区间,两个区间长度都为2j-1code

 1 //创建ST表,引自P2O5 dalao的blog:https://zybuluo.com/P2Oileen/note/816892#应用1-st表
 2 for(int a=1;a<=n;a++) f[a][0]=z[a];//z[]为源数据区间数组 
 3 for(int j=1;j<=logn;j++)
 4 {
 5     for(int i=1;i+z^j-1<=n;i++)
 6         f[i][j]=max(f[i][j-1],f[i+2^(j-1)][j-1]);
 7         //乘方是伪代码!
 8 }
 9 //solve
10 ans=max(f[l][k],f[r-2^k+1][k]);

因此就有两种状况

①假如区间长度(r-l+1)正好是2k,那么就直接访问f[l][k]

②假如区间长度(r-l+1)是2k+p(p<2k),也就说明2k≤(r-l+1)<2k+1,咱们能够慢慢地分治下去,利用前缀和,就造成了树状数组那样的东西,一段区间的最大值为 划分红两段区间最大值max1,max2相比取较大 ,可是这样太慢。

有一种更好的方法:其实咱们能够用两个长度为2k的区间就必定能把这段[l,r]区间完美覆盖起来,会有重复,可是对求最大值这件事情没有影响,因此 这段区间的最大值=max(f[l][k],f[r-2k+1][k])。

限制:不能用来计算区间和,由于中间有重复部分,还有就是:不支持修改ST表!

2.树上倍增LCA(最近公共祖先):

通常是用线性Tarjan算法来求解(这个Tarjan算法和图论中求有向图强连通份量的Tarjan不一样,都是一我的发明的),可是在ZHX十年的信息学奥赛生涯中没有用到这个算法,缘由有俩:①没遇到这样的题目②不会!(笑哭脸),有兴趣能够了解一下。

下面介绍倍增的算法:

定义:f[i][j]表示:从树上编号为i的节点向上走2j步会走到哪一个节点。

初始化:从j=0开始考虑,也就是从i号节点向上走1步到达的节点,就是i节点的父亲,因此:f[i][0]=father[i]。

转移:f[i][j]=f[f[i][j-1]][j-1],表示:从i节点往上走2j-1步后,再往上走2j-1步到达的点,等价于向上走2j步,由于2j-1+2j-1=2j。(j的范围大概[20,30)≈nlogn,只要保证2j>节点数目n便可

如今咱们构造出来这个f数组,下面介绍如何求两个点的LCA:

定义数组depth[i]表示i这个节点的深度,有如下两种状况

①depth[p1]==depth[p2],具备一个重要的性质:两个节点同时向上走一样地步数,深度仍然相等,也就是说,咱们让p1,p2一步一步地往上走,当走到同一个点时候,这个点必定是LCA!

 1 for(int x=19;x>=0;x--) 
 2 {
 3     if(f[p1][x]!=f[p2][x])//若是没有走到同一个点,继续往上走 
 4     {
 5         p1=f[p1][x];//p1往上跳 
 6         p2=f[p2][x];//p2往上跳 
 7     }     
 8 }    
 9 if(p1!=p2)//为何要加这个判断?有可能p1=p2么?是有可能的!这个判断是防止一开始跳以前p1=p2这种状况 
10 {
11     p1=f[p1][0];//由于上面的循环p1,p2只是走到了LCA的下方,这个判断只是处理最后一步:把p1或p2往上跳到它的父亲,就是LCA,返回便可 
12 } 
13 return p1;//p1为LCA,返回 

②depth[p1]!=depth[p2],假如p1比较深,就让p1往上跳到和p2深度同样的地方。

利用倍增f数组p1往上跳的方法:定义往上走步数step=depth[p1]-depth[p2],再利用二进制转换!

举个栗子:假如step=13,转为二进制=1101,能够得出13=8+4+1,:先走8步,再走4步,再走1步就行了。

1 int get_lca(int p1,int p2)
2 {
3     if(dpeth[p1]<depth[p2]) swap(p1,p2);
4     for(int x=19;x>=0;x--)
5     {
6     if((2^x)<=depth[p1]-depth[p2]) p1=f[p1][x];
7     }
8 }

下面是另外一种写法思路就很少讲,YY一下就能够出来的啦~

 1 int x=0;
 2 while (p1!=p2)
 3 {
 4     if(!x||f[p1][x]!=f[p2][x]) 
 5     {
 6         p1=f[p1][x];
 7         p2=f[p2][x];
 8         x++;        
 9     }
10     else x--;
11 }

3.快速幂:

按照通常思路,咱们要计算ax的话,要写一个for循环,计算a×a×a×a…这样太™麻烦而且浪费时间!

这里运用倍增来实现快速幂,这也是运用到了分治的思想。

咱们要求出x(x=2×k)个a的乘积,就能够分解为x/2个a的乘积的平方,这样就省去一半计算量,若是x是奇数,就在原先的基础上×a就能够了。

 1 int ksm(int a,int x)//求a^x的快速幂 时间复杂度:O(logx) 
 2 {
 3     int ans=1;
 4     while(x)
 5     {
 6         if(x&1) ans=(ans*a);//位运算:x&1==1则x为奇数
 7         a=a*a;
 8         x=x>>1;//位运算:右移一位,即将X除以2
 9     }
10     return ans;
11 }

2、分治算法

定义:将一个规模为N的问题分解为K个规模较小的子问题,这些子问题相互独立且与原问题性质相同。求出子问题的解,就可获得原问题的解。

这个算法的三个应用:

1.二分查找:

定义:给定排序数组,查询某个数是否在数组中

算法描述:在查找所要查找的元素时,首先与序列中间的元素进行比较,若是大于这个元素,就在当前序列的后半部分继续查找,若是小于这个元素,就在当前序列的前半部分继续查找,直到找到相同的元素,或者所查找的序列范围为空为止。

 1 bool find(int x)//二分查找x是否在序列z[]中 
 2 {
 3     left=0,right=n;
 4     while(left+1!=right)
 5     {
 6         middle=(left+right)>>1;
 7         if(z[middle]>=x) right=middle;
 8         else left=middle;
 9     }
10 }

还能够用lower_bound和upper_bound函数进行优化,用法详见下:

 1 #include <iostream>
 2 #include <algorithm>//必须包含的头文件,C++特有的库函数 
 3 using namespace std;
 4 int main()
 5 {
 6     int point[5]={1,3,7,7,9};
 7     int ans;
 8     /*两个函数传入的:(数组名,数组名+数组长度,要查找的数字),返回的是一个地址,减去数组名便可获得数字在数组中的下标*/
 9     ans=upper_bound(point,point+5,7)-point;//按从小到大,7最多能插入数组point的哪一个位置
10     printf("%d ",ans);//输出数字在数组中的下标 
11     ans=lower_bound(point,point+5,7)-point;////按从小到大,7最少能插入数组point的哪一个位置
12     printf("%d\n",ans);//输出数字在数组中的下标 
13     return 0;
14 }
15 /*
16 output:
17 4 2 
18 */

2.归并排序(nlogn):

分治法的典型应用

归并过程

比较a[i]和b[j]的大小,若a[i]≤b[j],则将第一个有序表中的元素a[i]复制到r[k]中,并令i和k分别加上1;

不然,将第二个有序表中的元素b[j]复制到r[k]中,并令j和k分别加上1。

如此循环下去,直到其中一个有序表取完,而后再将另外一个有序表中剩余的元素复制到r中从下标k到下标t的单元

归并排序的算法咱们一般用递归实现,先把待排序区间[s,t]以中点二分,接着把左边子区间排序,再把右边子区间排序,最后把左区间和右区间用一次归并操做合并成有序的区间[s,t]。

3.快速排序(nlogn):

通常在使用时候直接调用快排函数。

sort(快速排序,是C#特有的,不会退化为n2,比下面三个都要快,通常使用这个)

qsort(最坏状况下为n2,最好是n,指望为nlogn)

merge_sort(归并排序,稳定为nlongn)

heap_sort(堆排序,稳定为nlongn)

3、贪心算法:

算法思想在对问题求解时,老是作出在当前看来是最好的选择。也就是说,不从总体最优上加以考虑,他所作出的是在某种意义上的局部最优解。

贪心算法不是对全部问题都能获得总体最优解,关键是贪心策略的选择,选择的贪心策略必须具有无后效性,即某个状态之前的过程不会影响之后的状态,只与当前状态有关。
经典例题:选最大(野题)
给你N个数,要求你选出K个数使他们的和最大。
思路:咱们进行K次贪心,每次咱们都“贪”剩下的全部数中最大的,选择后拿走,这样就能保证我选的K个数总和最大。作法就是把N个数从大到小排序选择前K个便可。
经典例题:国王游戏(Luogu 1080)

  恰逢 H 国国庆,国王邀请 n 位大臣来玩一个有奖游戏。首先,他让每一个大臣在左、右手上面分别写下一个整数,国王本身也在左、右手上各写一个整数。而后,让这 n 位大臣排成一排,国王站在队伍的最前面。排好队后,全部的大臣都会得到国王奖赏的若干金币,每位大臣得到的金币数分别是:排在该大臣前面的全部人的左手上的数的乘积除以他本身右手上的数,而后向下取整获得的结果。

  国王不但愿某一个大臣得到特别多的奖赏,因此他想请你帮他从新安排一下队伍的顺序,使得得到奖赏最多的大臣,所获奖赏尽量的少。注意,国王的位置始终在队伍的最前面。

思路:此题要用到高精度,考虑最后一个大臣,显然他极可能是金币最多的人。咱们要让他的金币尽可能的少。以前的大臣左手的数确定会乘起来,因此咱们要使S/A/B尽可能的大。(设S是左手全部数的乘积),即让A*B尽可能的大。选完最后一个后,咱们把他踢掉,而后再找一个最后的大臣。如此往复,至关于就是对A*B排序。

基于交换的证实:假设两个相邻的大臣i,j,而前面的人总乘积为S。

当i在j前面时,设ans1=max(s/i.b,s*i.a/j.b);

当j在i前面时,设ans2=max(s/j.b,s*j.a/i.b);

因此要使得i在j前面,必需要ans1<ans2,因此:按照ai*bi排序,条件为x.a*x.b<y.a*y.b才是正解!

ZHX给的一个小小的Tip

当咱们作贪心时候不知道按照什么来排序,这时候就能够慢慢试,按照a×b、a+b、ab…这些条件来排序,看看哪一个计算出来的结果最符合标答^_^,真。。。是个有用的Tip啊!

4、搜索算法:

1.三种不一样的搜索问题:

①最优性问题:例如:有N个数,从中选出K个数知足和最大。

②计数问题:例如:有N个数,从中选出K个数的方案数目有多少。

③方案问题:例如:有N个数,从中选出K个数知足已知条件,输出其中M种方案,通常这第三类问题能够在第二类问题中解决

2.两种不一样的搜索方法:

①BFS(广度优先搜索):目的是系统地展开并检查图中的全部节点,以找寻结果。换句话说,它并不考虑结果的可能位置,完全地搜索整张图,直到找到结果为止。

②DFS(深度优先搜索):其过程简要来讲是对每个可能的分支路径深刻到不能再深刻为止,并且每一个节点只能访问一次。

3.搜索优化技巧:

①剪枝:当搜索到一个状态时候,发现当前状态不知足条件,就不继续往下搜,而不是等到搜索完毕才判断是否知足条件。

②BFS—meet in the middle:双向搜索(前提是要知道最终状态的样子)

举个栗子:咱们有一个满三叉树,深度为k,咱们搜索到最后一层的状态数为3k-1,这样要花费的时间很是多。

已知了起始状态和最终状态,要用BFS求出起始到最终所通过的那些状态,其实能够从起始状态日后走k/2个状态(k为初末总状态数),再从最终的N个状态往前走k/2个状态,必定会在中间某个节点相遇,这样联通起了整个状态。

时间、空间复杂度从O(X)变为O(sprt(X))

③重复结构利用(计数问题):这个我不会~

④卡时(最优化问题):咱们在面对求最优值问题的时候,若是找到一个更优的值,更新一下ans的值,可是若是你还没搜索完毕,时间已经快要到达上限了,这时候就会爆0,意思就是要在程序运行时间快要到达题目限制时候,把当前咱们找到的ans输出来!这样你就会从“得0分”变成“至少得0分”!超级有用啊啊!!

 1 #include <stdio.h>
 2 #include <stdlib.h>//exit()要用到的库函数
 3 #include <time.h>//clock()去官网看能不能用,不然就会变成至多零分了……
 4 int t;//程序开始运行时候当前系统时间 
 5 void dfs()
 6 {
 7     /*假如题目限制时间为2000ms*/
 8     if(clock()-t>=1995)//程序运行时间=程序执行到此当前系统时间-程序开始系统时间(以ms为单位) 
 9     {
10         shuchujie();//输出答案(保守一点可能须要5ms传入输出函数并输出) 
11         exit(0);//直接销毁程序! 
12     }
13     else
14     {
15         ;//继续往下搜 
16     }
17 }
18 int main()
19 {
20     t=clock();//记录一下当前时间
21     return 0; 
22 }

⑤数据结构优化:通常不会出到那么难(笑哭脸)

5、奇怪的东西:

愈来愈有用的东西——读入优化(能够加快读入int的速度):

咱们平时从一个文件里面读入一百万个数数据,基本接近一秒了(那还玩个P啊!),读入优化基本思想就是:把读入的数据拆成一个字符一个字符的来读入,由于字符读入比int快~

下面给出了读入优化int的代码,直接调用就能够了噢~固然只限于读入非负整数,若是是负数、小数、科学计数法的话还要加上一些判断。

这个什么原理相信各位看官稍加思考即可看明白。

 1 #include <stdio.h>
 2 int getint()
 3 {
 4     char c=' '; 
 5     while(c<'0'||c>'9') c=getchar(); 
 6     int x=0;//x为这个数字的前缀 
 7     while(c>='0'&&c<='9') 
 8     {
 9         x=x*10+c-'0';
10         c=getchar();
11     }
12     return x;
13 } 
14 int main()
15 {
16     int a;
17     a=getint();//调用读入优化 
18     printf("%d\n",a);
19     return 0;
20 }

最近发现一些网站盗用个人blog,这实在不能忍(™把关于个人名字什么的所有删去只保留文本啥意思。。)!!但愿各位转载引用时请注明出处,谢谢配合噢~

原博客惟一地址:http://www.cnblogs.com/geek-007/p/7296444.html

相关文章
相关标签/搜索