二分之「火車站台連鎖店」
二分是一種快速的查找方式,時間複雜度極低,為log2(r-l)(r為右邊界,l為左邊界,(r-l)為區間長度,log2(r-l)一般不超過100),非常實用,下面這道題便是一道經典的二分題;
火車站台連鎖店
描述
蒜頭君建立了一家火車站台連鎖店,要在一條鐵路線的所有車站裡,選擇一部分車站開辦連鎖店,銷售各種口味的大蒜。
鐵路線上有 n 個車站,假設這條鐵路線是一條直線,其中每個站點的坐標為 x1,x2,…,xn;
蒜頭君一共要開辦 m 個連鎖店,並且不希望連鎖店離得太近,以使得整體的收益最大化。他希望他的連鎖店之間的最近距離儘可能大,你能幫他算出這個最大的最近距離嗎?
輸入
第一行輸入用空格分隔的兩個整數 n,m(2 ≤ n ≤ 105,2 ≤ m ≤ n)分別表示車站數量和連鎖店數量。
接下來一共 n 行,每行一個整數 x_i(0 ≤ xi ≤ 109),表示車站的坐標。
輸出
輸出一行整數,表示最大的最近距離。
輸入樣例 1
6 3
1
3
5
2
7
9
輸出樣例 1
4
輸入樣例 2
5 4
5
7
10
28
9
輸出樣例 2
2
提示
樣例 2 說明
一共有 5 種選擇方案:
5 7 9 10:最近距離為 min(7-5,9-7,10-9)=1
5 7 9 28:最近距離為 min(7-5,9-7,28-9)=2
5 7 10 28:最近距離為 min(7-5,10-7,28-10)=2
7 9 10 28:最近距離為 min(9-7,10-9,28-10)=1
5 9 10 28:最近距離為 min(9-5,10-9,28-10)=1
因此,最大的最近距離為 2。
這道題,乍一眼,我的第一感覺是用dfs去做,但又轉念一想,這個的數據範圍很大,很可能會超時,於是,只能換種思路。
我又想到了dp去做這道題,但我實在是想不到怎麼用dp寫,只能放棄,用二分去做;
首先,我們先對數組進行排序,為二分做準備:
sort(a+1,a+1+n);//排序,因為二分必須需要完全的升序或降序排列
然後定義左邊界,右邊界,並為其賦值
int l=0,r=a[n]-a[1];//給左邊界,右邊界賦初值
接下來就是二分操作,但其實這不是真正的核心,真正的核心操作在後面:
while(r-l>1)//二分 { int mid=(l+r)/2; if(ok(mid)) l=mid; else r=mid; }
這是二分的基礎模板,可以進行記憶;
然後便是核心程式碼——ok函數:
bool ok(int x){ int tot=1;//表示選擇了的個數 int last=1; for(int i=1;i<=n;i++)//查找 { while(a[i]-a[last]<x)//如果比我們約定的最小值小,就i++ { i++; if(i>n) return tot>=m; // 如果多出了車站數,就return; } last=i; tot++; } return tot>=m; }
最後,補充上基礎結構和變數的定義,這道題就做出來了
#include<bits/stdc++.h> using namespace std; int n,m; int a[100010]; bool ok(int x){ int tot=1;//表示選擇了的個數 int last=1; for(int i=1;i<=n;i++)//查找 { while(a[i]-a[last]<x)//如果比我們約定的最小值小,就i++ { i++; if(i>n) return tot>=m; // 如果多出了車站數,就return; } last=i; tot++; } return tot>=m; } int main(){ cin>>n>>m; for(int i=1;i<=n;i++) cin>>a[i]; sort(a+1,a+1+n);//排序,因為二分必須需要完全的升序或降序排列 int l=0,r=a[n]-a[1];//給左邊界,右邊界賦初值 while(r-l>1)//二分 { int mid=(l+r)/2; if(ok(mid)) l=mid; else r=mid; } if(!ok(r)) r=l;//再檢查一次,檢查r是否正確答案 cout<<r; return 0; }