這是一道面試題,同時是codeforces上的c類題目。
tags:divide and conquer,dp,greedy
解決思路
首先明確
- 先需要明確每次只可能存在兩種刷法,要麼橫着一筆,要麼豎着一筆。
- 豎着刷不會存在“斷開”的問題,而橫着刷會存在。
思考過程
step1
因爲存在橫向斷開的問題,也就意味着可能需要分連續橫向區間處理,那麼首先應該想到用遞歸,每一層遞歸找到當前木材的最低高度,比這個高度更大的木材進入下一層遞歸,連續大於最低高度的木材爲同一個遞歸。看題目給的數據範圍,最多5000塊木板,那麼如果每一層遞歸僅僅只少1塊木材,遞歸深度也不會超過5000,這對於gcc或者g++編譯器來說是可以容忍的。
step2(建議看了代碼再來看這一部分)
實在想不出有什麼規律,那麼我們假設所有木材全部豎着刷,然後與所有木材橫着刷做比較,誰小誰就是結果。但是問題來了,橫着刷存在斷開的問題,不能一次性橫向全部刷完,而且可能會存在在橫着刷完後後面全部豎着刷可能次數更少。那就用遞歸,我們就找所有的木材中最小的高度,最小高度即爲橫向刷牆需要的次數,接着加上剩下牆刷的次數,就爲橫向刷牆的次數。然後把最小高度作爲分界線,得到多個連續的更高高度的新牆,然後對這些子問題進行上述相同處理。
最終思路
最後的最小次數一定小於等於全部豎着刷牆,對於橫着刷牆的情況,只有把0-最小高度刷完纔可能比全部豎着刷牆更小(橫向刷的範圍不能更高,也不能更小,只能等於最小高度纔可能取得最優解,至於爲什麼,也很好證明),所以我們需要討論這種情況,這就有了分治的思想。刷完以後,我們把“基線”移到最小高度之上,這樣就得到了多個連續的規模更小的新牆,這是與願問題相同的子問題,我們需要求子問題的最優解,願問題的最優解就包含了子問題的最優解(最優子結構),當前的狀態又是由橫向刷和豎向刷兩種情況中的最小值決定的。所以這道題就有了貪心、分治、動態規劃思想在裏面。
代碼
#include <iostream>
#include <limits.h>
#include <algorithm>
using namespace std;
//max re-deep is 5000,is ok
int dfs(int* a,int l,int r,int h)
{
if(l == r)
return 1;
int min_h = *min_element(a+l,a+r+1);
int res = min_h - h;
for(int i=l;i<=r;i++)
{
if(a[i] > min_h)
{
int j = i;
while(j < r && a[j+1] > min_h ) j++;
res += dfs(a,i,j,min_h);
i = j;
}
}
return min(res,r-l+1);
}
int main()
{
int n;
cin >> n;
int arr[n];
for(int i = 0;i<n;i++)
{
cin >> arr[i];
}
cout << dfs(arr,0,n-1,0);
return 0;
}