算法详解——二分
二分是一類比較常見、相對基礎的算法,其代碼往往是比較好寫的,并且格式也比較固定,同時二分也是一類出錯率比較高的算法,在你寫完一份二分的代碼放入測評機時,往往會發現你二分出的答案與正確答案比較接近,這都是正常現象,這也是二分比較難的地方——設定邊界。那么為了防止大家犯這種錯誤,我接下來將會對二分算法進行詳細的解析。
?
?
二分算法
?
給定一個長度為n的單調遞增序列a,要求我們在a中找到一個合法的ai,使得ai是a序列中>=x的數中最小的一個...
那么我們首先考慮,若存在一個合法的ai,那么i一定是ans或ans的后繼,那我們不妨設L=1,R=n,然后定義一個mid=(L+R)>>1,當amid>=x時,我們考慮mid右邊的數一定不會是ans,所以我們讓R=mid;同理,當amid<x時,讓L=mid+1(因為此時的L也不會是ans),這樣我們就構造出了一個合法的二分方法,代碼如下:
?
while(L < R) {int mid = (L + R)>>1;if(a[mid] >= x) R = mid;else L = mid + 1; } return a[L] //a[L]即為ans?
我們可以發現,這種二分方法只會取到L的值,卻不會取到R的值,那么這個結論有什么用呢?
我們現在改變題意,假設現在我們求的是a中滿足ai<=x的最大值,如果我們繼續用這種二分方法,你會發現,當發現amid正好與x相等時,L會變成mid+1,那么它就變成了ans的下一個數了,就不對了,也就是說,對于這種情況下,我們需要另一種二分方案:
?
while(L < R) {int mid = (L + R + 1)>>1;if(a[mid] <= x) L = mid;else R = mid - 1; } return a[L] //a[L]即為ans?
在上面的代碼中,對比發現,mid這時取的是(L+R+1)/ 2了, 我們考慮為什么這么做,那么我們可以發現,當R-L為奇時,它與(L+R)/ 2無異,當R-L為偶時,那么它取的是中間兩個元素的后者,而第一種代碼是選擇前者,所以,我們這樣取mid,就可以使amid與x相等時,ans取當前的mid值...
?
當然,關于二分的技巧還有很多,在這里我們先說明這兩種,接下來是一些二分的例題:
?
?
T1 洛谷P2678
題目背景
一年一度的“跳石頭”比賽又要開始了!
題目描述
這項比賽將在一條筆直的河道中進行,河道中分布著一些巨大巖石。組委會已經選擇好了兩塊巖石作為比賽起點和終點。在起點和終點之間,有?N?塊巖石(不含起點和終點的巖石)。在比賽過程中,選手們將從起點出發,每一步跳向相鄰的巖石,直至到達終點。
為了提高比賽難度,組委會計劃移走一些巖石,使得選手們在比賽過程中的最短跳躍距離盡可能長。由于預算限制,組委會至多從起點和終點之間移走?M?塊巖石(不能移走起點和終點的巖石)。
輸入輸出格式
輸入格式:
?
第一行包含三個整數?L,N,ML,N,M,分別表示起點到終點的距離,起點和終點之間的巖石數,以及組委會至多移走的巖石數。保證?L?≥1?且?N?≥?M?≥?0。
接下來?N?行,每行一個整數,第?i?行的整數?Di?(0<Di?<L), 表示第?i?塊巖石與起點的距離。這些巖石按與起點距離從小到大的順序給出,且不會有兩個巖石出現在同一個位置。
?
輸出格式:
?
一個整數,即最短跳躍距離的最大值。
?
?題解
本題是一個經典二分答案題,注意邊界,沒什么難度,代碼如下:
#include <cstdio> #include <cstring>int L,n,m; int d[50008];inline int read() //快讀 {char ch = getchar(); int x = 0,f = 1;while(ch<'0'||ch>'9'){if(ch == '-') f = -1; ch = getchar();}while(ch>='0'&&ch<='9'){x = x*10 + ch -'0'; ch = getchar();}return x*f; }inline bool check(int x) {int pre = 0; //上一個未被移除的石塊的位置 int move = 0; //需要移除的石塊個數 for(int i = 1;i <= n + 1;i ++){int dis = d[i] - d[pre];if(dis < x) move ++;else pre = i;}if(m >= move) return true;else return false; }int main() {L = read();n = read();m = read();for(int i = 1;i <= n;i ++) scanf("%d",&d[i]);d[n + 1] = L;int l = 0,r = L;int mid;while(l < r){mid = (l + r + 1)>>1;if(check(mid)) l = mid;else r = mid - 1;} printf("%d",l);return 0; }?
?
T2 洛谷P1439
?
題目描述
給出1-n的兩個排列P1和P2,求它們的最長公共子序列。
輸入輸出格式
輸入格式:
?
第一行是一個數n,
接下來兩行,每行為n個數,為自然數1-n的一個排列。
?
輸出格式:
?
一個數,即最長公共子序列的長度
?
?題解
本題是一道DP題,求的是最長公共子序列(最長不降子序列)
對,本來是最長公共子序列,但看一眼n≤100000,顯然n^2不可能,于是我們考慮nlogn的做法
我們可以發現,其中一個序列為單調遞增時,這個問題就轉化成了求一個序列的最長不降子序列,代碼如下:
?
#include <cstdio> #include <cstring> #include <algorithm>const int maxn = 1e5 + 5; int n; int f[maxn],a[maxn],b[maxn],map[maxn];int main() {scanf("%d",&n);for(int i = 1;i <= n;i ++) scanf("%d",&a[i]),map[a[i]] = i; for(int i = 1;i <= n;i ++) scanf("%d",&b[i]);std::sort(a + 1,a + n + 1);f[1] = map[b[1]];int cnt = 1;for(int i = 2;i <= n;i ++){if(f[cnt]<map[b[i]]) f[++cnt] = map[b[i]];else{int l = 1,r = cnt;while(l < r){int m = (l + r)>>1;if(f[m]>map[b[i]]) r = m;else l = m + 1;}f[l] = std::min(f[l],map[b[i]]);}}printf("%d",cnt);return 0; }?
轉載于:https://www.cnblogs.com/Ackers/p/9995982.html
總結
- 上一篇: linux平台下Tomcat的安装与优化
- 下一篇: Django组件-中间件