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

相關文章
相關標籤/搜索