hiho 修補木桶(二分)

時間限制: 10000msios

單點時限: 1000ms算法

內存限制: 256MBless

描述

一隻木桶能盛多少水,並不取決於桶壁上最高的那塊木板,而偏偏取決於桶壁上最短的那塊。函數

已知一個木桶的桶壁由N塊木板組成,第i塊木板的長度爲Ai。工具

如今小Hi有一個快捷修補工具,每次可使用修補工具將連續的不超過L塊木板提升至任意高度。優化

已知修補工具一共可使用M次(M*L<N),如何修補才能使最短的那塊木板最高呢?spa

注意: 木板是環形排列的,第N-1塊、第N塊和第1塊也被視爲連續的。內存

輸入

第1行:3個正整數,N, M, L。分別表示木板數量,修補工具使用次數,修補工具每次能夠同時修補的木板數。 1≤N≤1,000,1≤L≤20,M*L<Nstring

第2行:N個正整數,依次表示每一塊木板的高度Ai,1≤Ai≤100,000,000it

輸出

第1行:1個整數。表示使用修補工具後,最短木塊的所能達到的最高高度

樣例說明

第一個修補工具覆蓋[2 3 4]

第二個修補工具覆蓋[5 8 1]

 

樣例輸入

8 2 38 1 9 2 3 4 7 5

樣例輸出

7

 

《修補木桶》題目分析

本題可使用二分答案的思路解決。

咱們考慮這樣一個問題,假設最終最短的木板長度至少是K,最小須要使用修復工具幾回? 爲了描述方便咱們將這個最少次數記做F(K)。

因而咱們的問題變成求出最大的K,知足F(K) <= M。

若是咱們將F(K)當作一個函數,隨着K增長,咱們要修復的木板愈來愈多,顯然F(K)也會愈來愈大。

換句話說F(K)是單調遞增的。咱們能夠用二分來求出最大的K。

考慮 1 <= Ai <= 100000000,答案也必定在[1, 100000000]之間。在這個範圍內二分的複雜度是O(log(Max{Ai}))。

而後咱們的問題是對於肯定的K,計算F(K)。

當K肯定是,咱們就能夠肯定哪些木板須要被修復(Ai < K的木板)。

因爲木桶是環形的,咱們須要枚舉起點,複雜度O(N)。

一旦起點肯定,就能夠貪心求出每一次修復的位置。從而計算出F(K),複雜度O(N)。

因而總複雜度是O(log(Max{Ai})N^2)

這個算法能夠經過全部的數據。

不過上面算法中二分和計算F(K)都有優化的空間。

對於二分答案這部分,實際上答案必定是某個Ai,因此咱們能夠優化到O(logN)的二分。

對於計算F(K)的部分,考慮到修復範圍是L,因此O(N)的枚舉起點能夠優化到O(L)。

 

#include<cstdio>#include<iostream>#include<algorithm>#include<cstring>using namespace std;int n,m,l,a[2*1005],b[1005],rr,ll,mid;bool is_ok() {	if(b[0]==mid) return true;	//時間複雜度爲l*n	int cnt=0;//須要的段數	int cnt_start=0;//已開始的起點數	bool f1=false;	for(int i=0; i<n; ++i) {		int cnt=1,j,last;		if(a[i]<mid) {			last=i+n;			j=i+l;			++cnt_start;		} else continue;		while(j<last) {			if(a[j]<mid) {				++cnt;				j+=l;			} else ++j;		}		if(cnt<=m) {			f1=true;			break;		}		if(cnt_start>=l) break;	}	return f1;}int main() {	scanf("%d%d%d",&n,&m,&l);	for(int i=0; i<n; ++i)		scanf("%d",a+i);	memcpy(b,a,n*sizeof(int));	memcpy(a+n,a,n*sizeof(int));	sort(b,b+n,less<int>());	ll=0,rr=n-1;	while(ll<rr) {		int t=(ll+rr+1)/2;//這個+1要注意		mid=b[t];		if(is_ok()) {			ll=t;		} else {			rr=t-1;//這個地方要注意下		}	}	cout<<b[ll]<<endl;	return 0;}
相關文章
相關標籤/搜索