[COCI 2010] OGRADA
題目
Description
Matija 的柵欄由N條木板組成,從左到右依次編號為1…N。i號木板的高度為hi,每條木板的寬度是1cm。
Matija 想用一個寬度為Xcm的滾筒刷來刷木板。使用滾筒刷時,要保證刷子完全接觸柵欄(不能一部分接觸一部分不接觸);另外,還要保證滾筒平行於地面。因此,每次塗色時,Matija 會在柵欄上選擇連續的x條木板,然後從下往上「刷」,一直刷到這x條木板中最矮者的高度。
根據上述規則,有可能有一些木板沒法用滾筒刷來刷,Matija 不得不用牙刷來「塗」剩下的部分。因此,請幫他求出他最少只需用牙刷「塗」多少平方釐米。他還想知道,在滿足「塗」的面積最少的情況下,他最少要用滾筒刷「刷」多少次。
Input
第一行給出數字N,X.
第二行給出N個數字,代表每個木板的高度.
Output
兩行。
第一行有一個整數,表示 Matija 最少需用牙刷塗多少平方釐米。
第二行有一個整數,表示最少要用滾筒刷「刷」多少次。
Sample Input1
5 3 5 3 4 4 5
Sample Output1
3 2
Sample Input2
10 3 1 7 7 6 7 10 2 1 8 4
Sample Output2
17 5
思路
根據題目 樣例1
$n=5~,k=3$ ;
$a[1]=5~,~a[2]=3~,~a[3]=4~,~a[4]=4~,~a[5]=5$
我們需要滿足題目刷的限制,保證刷子完全接觸柵欄,也就是每次刷的時候不能刷到空的;
那麼對於 $i$ 到 $i+k-1$ 刷的高度就是 $min(a[j])~,~1 \leq j\& \& j \leq i+k-1$ ;
我們設一個 $k$ 陣列把這個高度記下;
$h[i]$ 代表從 $i$ 到 $i+k-1$ 能刷到的高度;
我們做一遍單調佇列就可以求出$min(a[j])~,~1 \leq j\& \& j \leq i+k-1$ ,也就是 $h[i]$ ;
求出後 $h[1]=3$ , $h[2]=3$ , $h[3]=4$ ;
很明顯每條木板都有一個,能刷的最大高度;
如樣例:
$mx[1]=3~,~mx[2]=3~,~mx[3]=4~,~mx[4]=4~,~mx[5]=4$ ;
我們會發現 $mx[3]$ 可以刷到的$h[i]$ 有兩個 $3$ 和 $4$ ;
但是我們要取 $3$ 的 $max(h[i])$ ,也就是取 $4$;
所以$mx[i]=max(h[j])~,~i-k+1 \leq j\& \& j \leq i$ ;
再做一遍單調佇列求出每個 $mx[i]$ 就ok了;
這樣刷不到的地方就是 $\sum ^n _i a[i]-mx[i] $ ;
這樣我們就很輕鬆地解決了第一問;
那麼第二問怎麼求呢?
首先在$i+k-1$ 這個範圍內,如果 $mx[i] \neq mx[j]$ 那麼 ,$ans++$ ;
刷的最大高度不同,則說明在這個範圍內刷了多次;
如樣例
在$a[1]$ 到 $a[3]$ 之間 $mx[1] \neq mx[3]$ ,則這個範圍內刷了$2$ 次;
如果刷的區域超出了上次刷的寬度,那麼說明又刷了一次;
什麼意思呢?
我們來看下面這個特殊的樣例
$n=5~,~k=3$ ,$a[i]=3~,~1 \leq i\& \& i \leq n $;
這個樣例中沒有出現不同的$mx[i]$,但是很明顯也刷了兩次;
第一次從$1$ 刷到了 $3$ ,需要刷的區域 $4$ 超出的這個範圍,說明再刷了一次;
所以如果刷的區域超出了上次刷的寬度,那麼說明又刷了一次;
所以我們可以設一個 $pre$ 表示上次的$mx[i]$ ,設 $pra$ 表示上次刷的寬度,也表示刷到了 $i$;
那麼
if(mx[i]!=pre) //如果刷的高度不同,說明從 i 又刷了一次 { ans++;//答案加1 pra=i+k-1;//記錄下刷到的邊界 } if(i>pra)//如果超出了這個邊界,說明後來 i 刷了一次 { ans++;//統計答案 pra=i-k+1;//更新邊界 }
這樣統計$ans$ 就ok了;
同樣就輕鬆地解決了第二個問題;
那麼沒有第三個問題了,就直接上程式碼吧;
程式碼
#include<bits/stdc++.h> #define re register typedef long long ll;//被坑過一次了,習慣每次都開long long 了 using namespace std; inline ll read() { ll a=0,f=1; char c=getchar(); while (c<'0'||c>'9') {if (c=='-') f=-1; c=getchar();} while (c>='0'&&c<='9') {a=a*10+c-'0'; c=getchar();} return a*f; }//快讀比 scanf 好打多了 ll n,k; ll a[1000010],h[1000010],mx[1000010]; ll q[1000010]; int main() { n=read(); k=read(); for(re ll i=1;i<=n;i++) a[i]=read(); ll head=1,tail=0;//單調佇列初始頭尾 for(re ll i=1;i<=n;i++) { while(head<=tail&&i-q[head]+1>k)//如果長度超過刷子寬度 k head++;// 那就踢隊頭 while(head<=tail&&a[q[tail]]>a[i])//找一個最小值,因為不能刷到空白,所以刷的高度就不能高於木板最小值 tail--;// 踢隊尾 q[++tail]=i;//入隊 if(i-k+1>=0)//下標不為0 h[i-k+1]=a[q[head]];//記錄 } head=1,tail=0;//重置 for(re ll i=1;i<=n;i++) { while(head<=tail&&i-q[head]+1>k)//限制範圍 head++;// 又踢隊頭 while(head<=tail&&h[q[tail]]<h[i])//找一個h[i]的最大值 tail--;// 踢掉,踢掉!!! q[++tail]=i;//入隊 mx[i]=h[q[head]];//再來記錄 } ll sum=0; for(re ll i=1;i<=n;i++)//For sum+=a[i]-mx[i]; printf("%lld\n",sum);//輸出第一個問題的答案 ll ans=0; ll pre=0,pra=1<<30;//不需要設為1<<30,只是博主之前用另一種方法寫的時候忘改了 for(re ll i=1;i<=n;i++) { if(mx[i]!=pre) //如果刷的高度不同,說明從 i 又刷了一次 { ans++;//答案加1 pra=i+k-1;//記錄下刷到的邊界 } if(i>pra)//如果超出了這個邊界,說明後來 i 刷了一次 { ans++;//統計答案 pra=i-k+1;//更新邊界 } } printf("%lld\n",ans);//輸出 //return 0; }