日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

NOIP训练营集训笔记—信息学基础算法(倍增与分治算法

發(fā)布時(shí)間:2023/12/9 编程问答 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 NOIP训练营集训笔记—信息学基础算法(倍增与分治算法 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

?

本文摘自清北O(jiān)I學(xué)堂內(nèi)部筆記,作者潘愷璠,來自柳鐵一中曾參加過清北訓(xùn)練營提高組精英班,主要記錄的是信息學(xué)基礎(chǔ)算法。筆記非常詳細(xì),特分享給大家! NOIP2019年夏令營正在報(bào)名中,6大校區(qū)10種班型,可前往微信noipnoi報(bào)名!

一、倍增算法:

定義:用f[i][j]表示從i位置出發(fā)的2j個(gè)位置的信息綜合(狀態(tài))

一個(gè)小小的問題:為什么是2j而不是3j,5j,…?因?yàn)?#xff0c;假設(shè)為kj,整個(gè)算法的時(shí)間復(fù)雜度為(k-1)logk,當(dāng)k=2時(shí),時(shí)間復(fù)雜度最小。

這個(gè)算法的三個(gè)應(yīng)用:

1.倍增ST表:

應(yīng)用:這個(gè)ST表是用來解決RMQ問題(給你n個(gè)數(shù),m次詢問,每次詢問[l,r]這個(gè)區(qū)間的最大值),當(dāng)然,解決RMQ問題是可以用線段樹來做的,但是比較麻煩,NOIP 80%是不會(huì)用線段樹來做,還是用ST表方便。

定義:f[i][j]表示:從i到i+2j-1這2j個(gè)位置的元素最大值

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

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

//建立ST表,引自P2O5 dalao的blog:https://zybuluo.com/P2Oileen/note/816892#應(yīng)用1-st表

for(int a=1;a<=n;a++) f[a][0]=z[a];//z[]為源數(shù)據(jù)區(qū)間數(shù)組?

for(int j=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]);

所以就有兩種情況:

①假如區(qū)間長度(r-l+1)正好是2k,那么就直接訪問f[l][k];

②假如區(qū)間長度(r-l+1)是2k+p(p<2k),也就說明2k≤(r-l+1)<2k+1,我們可以慢慢地分治下去,利用前綴和,就形成了樹狀數(shù)組那樣的東西,一段區(qū)間的最大值為 劃分成兩段區(qū)間最大值max1,max2相比取較大 ,但是這樣太慢。

有一種更好的方法:其實(shí)我們可以用兩個(gè)長度為2k的區(qū)間就一定能把這段[l,r]區(qū)間完美覆蓋起來,會(huì)有重復(fù),但是對求最大值這件事情沒有影響,所以?這段區(qū)間的最大值=max(f[l][k],f[r-2k+1][k])。

限制:不能用來計(jì)算區(qū)間和,因?yàn)橹虚g有重復(fù)部分,還有就是:不支持修改ST表!

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

一般是用線性Tarjan算法來求解(這個(gè)Tarjan算法和圖論中求有向圖強(qiáng)連通分量的Tarjan不同,都是一個(gè)人發(fā)明的),但是在ZHX十年的信息學(xué)奧賽生涯中沒有用到這個(gè)算法,原因有倆:①?zèng)]遇到這樣的題目②不會(huì)!(笑哭臉),有興趣可以了解一下。

下面介紹倍增的算法:

定義:f[i][j]表示:從樹上編號為i的節(jié)點(diǎn)向上走2j步會(huì)走到哪個(gè)節(jié)點(diǎn)。

初始化:從j=0開始考慮,也就是從i號節(jié)點(diǎn)向上走1步到達(dá)的節(jié)點(diǎn),就是i節(jié)點(diǎn)的父親,所以:f[i][0]=father[i]。

轉(zhuǎn)移:f[i][j]=f[f[i][j-1]][j-1],表示:從i節(jié)點(diǎn)往上走2j-1步后,再往上走2j-1步到達(dá)的點(diǎn),等價(jià)于向上走2j步,因?yàn)?j-1+2j-1=2j。(j的范圍大概[20,30)≈nlogn,只要保證2j>節(jié)點(diǎn)數(shù)目n即可)

現(xiàn)在我們構(gòu)造出來這個(gè)f數(shù)組,下面介紹如何求兩個(gè)點(diǎn)的LCA:

定義數(shù)組depth[i]表示i這個(gè)節(jié)點(diǎn)的深度,有以下兩種情況:

①depth[p1]==depth[p2],具有一個(gè)重要的性質(zhì):兩個(gè)節(jié)點(diǎn)同時(shí)向上走同樣地步數(shù),深度仍然相等,也就是說,我們讓p1,p2一步一步地往上走,當(dāng)走到同一個(gè)點(diǎn)時(shí)候,這個(gè)點(diǎn)一定是LCA!

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

{

? ? if(f[p1][x]!=f[p2][x])//如果沒有走到同一個(gè)點(diǎn),繼續(xù)往上走?

? ? {

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

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

? ? }? ? ?

}? ??

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

{

? ? p1=f[p1][0];//因?yàn)樯厦娴难h(huán)p1,p2只是走到了LCA的下方,這個(gè)判斷只是處理最后一步:把p1或p2往上跳到它的父親,就是LCA,返回即可?

}?

return p1;//p1為LCA,返回

?

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

利用倍增f數(shù)組p1往上跳的方法:定義往上走步數(shù)step=depth[p1]-depth[p2],再利用二進(jìn)制轉(zhuǎn)換!

舉個(gè)栗子:假如step=13,轉(zhuǎn)為二進(jìn)制=1101,可以得出13=8+4+1,:先走8步,再走4步,再走1步就好了。

int get_lca(int p1,int p2)

{

? ? 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];

? ? }

}

?

下面是另一種寫法思路就不多講

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.快速冪:

按照一般思路,我們要計(jì)算ax的話,要寫一個(gè)for循環(huán),計(jì)算a×a×a×a…這樣太?麻煩并且浪費(fèi)時(shí)間!

這里運(yùn)用倍增來實(shí)現(xiàn)快速冪,這也是運(yùn)用到了分治的思想。

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

int ksm(int a,int x)//求a^x的快速冪 時(shí)間復(fù)雜度:O(logx)?

{

? ? int ans=1;

? ? while(x)

? ? {

? ? ? ? if(x&1) ans=(ans*a);//位運(yùn)算:x&1==1則x為奇數(shù)

? ? ? ? a=a*a;

? ? ? ? x=x>>1;//位運(yùn)算:右移一位,即將X除以2

? ? }

? ? return ans;

}

?

二、分治算法:

定義:將一個(gè)規(guī)模為N的問題分解為K個(gè)規(guī)模較小的子問題,這些子問題相互獨(dú)立且與原問題性質(zhì)相同。求出子問題的解,就可得到原問題的解。

這個(gè)算法的三個(gè)應(yīng)用:

1.二分查找:

定義:給定排序數(shù)組,查詢某個(gè)數(shù)是否在數(shù)組中

算法描述:在查找所要查找的元素時(shí),首先與序列中間的元素進(jìn)行比較,如果大于這個(gè)元素,就在當(dāng)前序列的后半部分繼續(xù)查找,如果小于這個(gè)元素,就在當(dāng)前序列的前半部分繼續(xù)查找,直到找到相同的元素,或者所查找的序列范圍為空為止。

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_bound和upper_bound函數(shù)進(jìn)行優(yōu)化,用法詳見下:

#include <iostream>

#include <algorithm>//必須包含的頭文件,C++特有的庫函數(shù)?

using namespace std;

int main()

{

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

? ? int ans;

? ? /*兩個(gè)函數(shù)傳入的:(數(shù)組名,數(shù)組名+數(shù)組長度,要查找的數(shù)字),返回的是一個(gè)地址,減去數(shù)組名即可得到數(shù)字在數(shù)組中的下標(biāo)*/

? ? ans=upper_bound(point,point+5,7)-point;//按從小到大,7最多能插入數(shù)組point的哪個(gè)位置

? ? printf("%d ",ans);//輸出數(shù)字在數(shù)組中的下標(biāo)?

? ? ans=lower_bound(point,point+5,7)-point;按從小到大,7最少能插入數(shù)組point的哪個(gè)位置

? ? printf("%d\n",ans);//輸出數(shù)字在數(shù)組中的下標(biāo)?

? ? return 0;

}

/*

output:

4 2?

*/

?

2.歸并排序(nlogn):

是分治法的典型應(yīng)用。

歸并過程:

比較a[i]和b[j]的大小,若a[i]≤b[j],則將第一個(gè)有序表中的元素a[i]復(fù)制到r[k]中,并令i和k分別加上1;

否則,將第二個(gè)有序表中的元素b[j]復(fù)制到r[k]中,并令j和k分別加上1。

如此循環(huán)下去,直到其中一個(gè)有序表取完,然后再將另一個(gè)有序表中剩余的元素復(fù)制到r中從下標(biāo)k到下標(biāo)t的單元

歸并排序的算法我們通常用遞歸實(shí)現(xiàn),先把待排序區(qū)間[s,t]以中點(diǎn)二分,接著把左邊子區(qū)間排序,再把右邊子區(qū)間排序,最后把左區(qū)間和右區(qū)間用一次歸并操作合并成有序的區(qū)間[s,t]。

3.快速排序(nlogn):

一般在使用時(shí)候直接調(diào)用快排函數(shù)。

sort(快速排序,是C#特有的,不會(huì)退化為n2,比下面三個(gè)都要快,一般使用這個(gè))

qsort(最壞情況下為n2,最好是n,期望為nlogn)

merge_sort(歸并排序,穩(wěn)定為nlongn)

heap_sort(堆排序,穩(wěn)定為nlongn)

?

轉(zhuǎn)載于:https://www.cnblogs.com/qbxt/p/10984714.html

總結(jié)

以上是生活随笔為你收集整理的NOIP训练营集训笔记—信息学基础算法(倍增与分治算法的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。