1. 程式人生 > >NOIP複賽複習(十一)基礎演算法鞏固與提高

NOIP複賽複習(十一)基礎演算法鞏固與提高

一、倍增演算法:

 

定義:用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]表示:從

ii+2j-12j個位置的元素最大值

初始化f[i][0]=z[i](第i個位置到第i+20-1個位置的最大值,對應只有一個元素的區間)

轉移f[i][j]=max(f[i][j-1],f[i+2(j-1)][j-1])(把[i,i+2j-1]這個區間分治為兩個區間,這兩個區間拼在一起就成了原來一個大的區間,兩個區間長度都為2j-1

 

//建立ST表,引自P2O5dalaobloghttps://zybuluo.com/P2Oileen/note/816892#應用1-st

for(inta=1;a<=n;a++) f[a][0]=z[a];//z[]為源資料區間陣列

for(intj=1;j<=logn;j++)

{

    for(int i=1;i+z^j-1<=n;i++)

       f[i][j]=max(f[i][j-1],f[i+2^(j-1)][j-1]);

        //乘方是虛擬碼!

}

//solve

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不同,都是一個人發明的),但是很難用到這個演算法,原因有倆:沒遇到這樣的題目不會!(笑哭臉),有興趣可以瞭解一下。

下面介紹倍增的演算法:

定義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

 

for(intx=19;x>=0;x--)

{

    if(f[p1][x]!=f[p2][x])//如果沒有走到同一個點,繼續往上走

    {

        p1=f[p1][x];//p1往上跳

        p2=f[p2][x];//p2往上跳

    }    

}   

if(p1!=p2)//為什麼要加這個判斷?有可能p1=p2麼?是有可能的!這個判斷是防止一開始跳之前p1=p2這種情況

{

    p1=f[p1][0];//因為上面的迴圈p1p2只是走到了LCA的下方,這個判斷只是處理最後一步:把p1p2往上跳到它的父親,就是LCA,返回即可

}

return p1;//p1LCA,返回

 

depth[p1]!=depth[p2],假如p1比較深,就讓p1往上跳到和p2深度一樣的地方。

利用倍增f陣列p1往上跳的方法:定義往上走步數step=depth[p1]-depth[p2],再利用二進位制轉換!

舉個栗子:假如step=13,轉為二進位制=1101,可以得出13=8+4+1,:先走8步,再走4步,再走1步就好了。

 

int get_lca(int p1,intp2)

{

    if(dpeth[p1]<depth[p2]) swap(p1,p2);

    for(int x=19;x>=0;x--)

    {

    if((2^x)<=depth[p1]-depth[p2])p1=f[p1][x];

    }

}


下面是另一種寫法思路就不多講,YY一下就可以出來的啦~

 

int x=0;

while (p1!=p2)

{

    if(!x||f[p1][x]!=f[p2][x])

    {

        p1=f[p1][x];

        p2=f[p2][x];

        x++;       

    }

    else x--;

}

 

3.快速冪:

 

按照一般思路,我們要計算ax的話,要寫一個for迴圈,計算a×a×a×a…這樣太麻煩並且浪費時間!

這裡運用倍增來實現快速冪,這也是運用到了分治的思想。

我們要求出x(x=2×k)a的乘積,就可以分解為x/2a的乘積的平方,這樣就省去一半計算量,如果x是奇數,就在原先的基礎上×a就可以了。

 

int pow(int a,int x)//a^x的快速冪時間複雜度:O(logx)

{

    int ans=1;

    while(x)

    {

        if(x&1) ans=(ans*a);//位運算:x&1==1x為奇數

        a=a*a;

        x=x>>1;//位運算:右移一位,即將X除以2

    }

    return ans;

}


二、分治演算法:

 

定義:將一個規模為N的問題分解為K個規模較小的子問題,這些子問題相互獨立且與原問題性質相同。求出子問題的解,就可得到原問題的解。

這個演算法的三個應用:

 

1.二分查詢:

 

定義:給定排序陣列,查詢某個數是否在陣列中 

演算法描述:在查詢所要查詢的元素時,首先與序列中間的元素進行比較,如果大於這個元素,就在當前序列的後半部分繼續查詢,如果小於這個元素,就在當前序列的前半部分繼續查詢,直到找到相同的元素,或者所查詢的序列範圍為空為止。

 

bool find(int x)//二分查詢x是否在序列z[]

{

    left=0,right=n;

    while(left+1!=right)

    {

        middle=(left+right)>>1;

        if(z[middle]>=x) right=middle;

        else left=middle;

    }

}


還可以用lower_boundupper_bound函式進行優化,用法詳見下:

 

#include<iostream>

#include<algorithm>//必須包含的標頭檔案,C++特有的庫函式

using namespace std;

int main()

{

    int point[5]={1,3,7,7,9};

    int ans;

    /*兩個函式傳入的:(陣列名,陣列名+陣列長度,要查詢的數字),返回的是一個地址,減去陣列名即可得到數字在陣列中的下標*/

    ans=upper_bound(point,point+5,7)-point;//按從小到大,7最多能插入陣列point的哪個位置

    printf("%d ",ans);//輸出數字在陣列中的下標

    ans=lower_bound(point,point+5,7)-point;////按從小到大,7最少能插入陣列point的哪個位置

    printf("%d\n",ans);//輸出數字在陣列中的下標

    return 0;

}

/*

output

相關推薦

NOIP複賽複習基礎演算法鞏固提高

一、倍增演算法:   定義:用f[i][j]表示從i位置出發的2j個位置的資訊綜合(狀態) 一個小小的問題:為什麼是2j而不是3j,5j,…?因為,假設為kj,整個演算法的時間複雜度為(k-1)logk,當k=2時,時間複雜度最小。 這個演算法的三個應用:

NOIP複賽複習字串演算法鞏固提高

一、Trie樹   1.定義: 通過字串建成一棵樹,這棵樹的節點個數一定是最少的。例如:4個字串"ab","abc","bd","dda"對應的trie樹如下: 其中紅色節點表示存在一個字串是以這個點結尾的。  一個性質:在樹上,兩個點u,

NOIP複賽複習數論演算法鞏固提高

一、數論    1.數   整數、自然數(大於等於0的整數)、正整數(大於0的整數)、負整數、非負整數、非正整數、非零整數、奇數偶數。   2.整除性   設a,b∈Z,如果存在c∈Z並且a=bc,則

NOIP複賽複習動態規劃鞏固提高

經典例題:數字金字塔(Luogu 1216)  寫一個程式來查詢從最高點到底部任意處結束的路徑,使路徑經過數字的和最大。每一步可以走到左下方的點也可以到達右下方的點。 我們現在這裡討論搜尋如何實現:  狀態:目前在第x行第y列 

NOIP複賽複習怎樣才能拿到高分?

摘要 考場策略和程式測試是資訊學競賽中非常重要的環節,很多優秀的選手在很多比賽中總是會在這兩個環節上犯下這樣和那樣的錯誤,導致得到的分數和實力不成正比,最後留下了無盡的遺憾。本文將探討一些這兩個環節上值得注意的地方,提出一些可行的方法,分享一些經驗,以此希望幫助選手們在比賽中發揮水平,減少失

深入Asyncio優雅地開始結束

關於 adl == blocking 無法 捕獲 連接建立 server got Startup and Shutdown Graceful 大部分基於asyncio的程序都是需要長期運行、基於網絡的應用,處理這種應用的正確開啟與關閉存在驚人的復雜性。 開啟相對來說更簡單點

工作那些事談談碼農農民工區別和發展之路 工作那些事如果哪天,沒有了電腦 工作那些事十三再次失業

工作那些事系列連結快速通道,不斷更新中: 工作那些事(一)今年工作不好找 工作那些事(二)應聘時填寫個人資訊ABCD 工作那些事(三)什麼樣的公司能吸引你,什麼樣的公司適合你? 工作那些事(四)大公司VS小公司 工作那些事(五)談談專案資料整理和積累 工作那些事(六)談談

深入理解JVM——Java記憶體模型執行緒

計算機運算的速度,與它的儲存和通訊子系統相差太大,大量的時間花費在磁碟IO,網路通訊和資料庫上。 衡量一個服務效能的高低好壞,每秒事務處理數TPS是最重要的指標。 對於計算量相同的任務,程式執行緒併發協調的越有條不紊,效率越高;反之,執行緒之間頻繁阻塞或是死鎖,將大大降低併發能力。

jdk原始碼解析——Java記憶體模型執行緒

前面我們瞭解了Java的編譯和執行,這裡在講解一下高效併發(Java記憶體模型與執行緒)在瞭解記憶體模型與執行緒之前,我們先要了解一些東西。 1 硬體效率與一致性  計算併發執行的執行和充分利用計算機處理器的效能兩者看來是互為因果的,而在大多數的時候,計算機的處理速度不止是在處理器

學習OpenCV範例——影象的腐蝕膨脹

這次範例相對比較簡單,是涉及到形態學操作的問題,原理也是比較簡單,學習起來比較輕鬆,大家看完這次的範例分析就可以明白到底影象的腐蝕和膨脹是怎麼回事了。 1、原理 簡單來講,形態學操作就是基於形狀的一系列影象處理操作。通過將 結構元素 作用於輸入影象來產生輸出影象。 最基本的

NOIP複賽複習常見問題常用策略

數學類問題 1. 精度處理(高精度、實數處理、各種浮點型別處理方法) 2. 組合數學問題(斐波那契數列、第二類數、卡特蘭數、Polya原理、排列組合計數、加法原理與乘法原理) 3. 進位制問題(特定二進位制串的統計、二分查詢、利用二進位制進行路徑、狀

python基礎教程

list repeat stop row lis flatten ror 教程 [1] 叠代器 本節進行叠代器的討論。只討論一個特殊方法---- __iter__ ,這個方法是叠代器規則的基礎。 叠代器規則 叠代的意思是重復做一些事很多次---就像在循環中做的

Java基礎 面試題

Java基礎 程序員面試 反射 面試題: 如何獲取class字節碼文件對象/Class的類對象 三種方式來獲取這個class字節碼文件對象: 1)Object中的getClass() 2)任何數據類型的靜態屬性class 3)Class類中的方

Java基礎

tcp編程 反射 Java基礎 Javase (一)TCP編程(1)TCP(建立連接通道)編程的客戶端的開發步驟 1)創建客戶端的Socket對象 Socket:就是客戶端的Socket 構造方法 public Socket(InetAddress address,

C#基礎_值類型引用類型

基本 形式參數 alt ID per 畫圖 hang write 數據 值類型和引用類型:   值類型包括:byte,short,int,long,char,float,double,bool,struct結構   引用類型包括:類類型,接口類型和數組 等。 值類型

c++基礎

自動 分享 類型 默認 void size out -s derived 類的繼承的概念 繼承是在保持已有類的基礎之上構造新類的過程,而派生是指在已有類的基礎之上新增自己的特性而產生新類的過程。二者是對同一個問題的不同描述,繼承側重於保持原有類的特性,而派生側重於增加新的特

Java基礎入門之基本數據包裝類以及簡單轉換

數據包 intvalue nbsp 1.5 lse false 永遠 ring jdk 一、 基本數據類型包裝類 引用數據類型一般為基本數據類型首字母大寫,除了int 、char,其中int的引用數據類型類Integer,char的引用數據類型為Character 關

NOIP複賽複習十三圖論演算法鞏固提高

一、圖的儲存   1、鄰接矩陣   假設有n個節點,建立一個n×n的矩陣,第i號節點能到達第j號節點就將[i][j]標記為1(有權值標記為權值),  樣例如下圖:   /*無向圖,無權值*/ i

NOIP複賽複習如何設計測試資料?

有些同學參加一次資訊學比賽之後,自我感覺非常不錯,但是測評結果成績卻並不理想。造成這種情況的原因有多方面,但是我認為其中不可忽視的一大原因就是在寫完程式之後,他們並不知道如何保證程式的正確性。在這裡,我就這個問題提出一點自己的看法。 先從考試的時間分配問題講起。很多同學覺得考試時間很充分,N

NOIP複賽複習STL演算法樹結構模板

STL演算法 STL 演算法是一些模板函式,提供了相當多的有用演算法和操作,從簡單如for_each(遍歷)到複雜如stable_sort(穩定排序),標頭檔案是:#include <algorithm>。常用STL 演算法庫包括:sort快速排序演算法、二分