尺取法及例題
尺取法:顧名思義,像尺子一樣取一段,借用挑戰書上面的話說,尺取法通常是對數組保存一對下標,即所選取的區間的左右端點,然後根據實際情況不斷地推進區間左右端點以得出答案。之所以需要掌握這個技巧,是因爲尺取法比直接暴力枚舉區間效率高很多,尤其是數據量大的時候,所以尺取法是一種高效的枚舉區間的方法,一般用於求取有一定限制的區間個數或最短的區間等等。當然任何技巧都存在其不足的地方,有些情況下尺取法不可行,無法得出正確答案。
============================================================================
使用尺取法應該注意下列幾點
1、 什麼情況下能使用尺取法? 2、何時推進區間的端點? 3、如何推進區間的端點? 4、何時結束區間的枚舉?
尺取法通常適用於選取區間有一定規律,或者說所選取的區間有一定的變化趨勢的情況,通俗地說,在對所選取區間進行判斷之後,我們可以明確如何進一步有方向地推進區間端點以求解滿足條件的區間,如果已經判斷了目前所選取的區間,但卻無法確定所要求解的區間如何進一步得到根據其端點得到,那麼尺取法便是不可行的。首先,明確題目所需要求解的量之後,區間左右端點一般從最整個數組的起點開始,之後判斷區間是否符合條件在根據實際情況變化區間的端點求解答案。
下面我們來看一個例子
![在這裏插入圖片描述](https://img-blog.csdnimg.cn/20190408213214880.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1N0aWxsYm9yaW5n,size_16,color_FFFFFF,t_70`
下面是僞代碼實現:
int l,r,sum,i,s;
i=sum=l=0;
result=n+1;
while(1)
{
while(i<n&&sum<s)//從左端點開始
{
sum+=a[i];
i++;
}
if(sum<s)//條件不滿足了,就退出循環
{
break;
}
MIN=i-l;
result=min(MIN,result);
sum-=a[l++];//尺取法左端點前進
}
1、 Poj3061題意:給定一個序列,找出最短的子序列長度,使得其和大於或等於S。
分析:首先,序列都是正數,如果一個區間其和大於等於S了,那麼不需要在向後推進右端點了,因爲其和也肯定大於等於S但長度更長,所以,當區間和小於S時右端點向右移動,和大於等於S時,左端點向右移動以進一步找到最短的區間,如果右端點移動到區間末尾其和還不大於等於S,結束區間的枚舉。
#include <cstdio>
#include <algorithm>
#include <cstring>
#define MAX 100005
#define LL long long
#define INF 0x3f3f3f3f
using namespace std;
LL a[100010];
int n, t, ans = INF;
LL sum, s;
int main()
{
scanf("%d", &t);
while (t--){
scanf("%d %I64d", &n, &s);
for (int i = 0; i < n; i++) scanf("%I64d", a+i);
int st = 0, en = 0;
ans = INF; sum = 0;
while (1){
while (en<n && sum<s) sum += a[en++];
if (sum < s) break;
ans = min(ans, en-st);
sum -= a[st++];
}
if (ans == INF) ans = 0;
printf("%d\n", ans);
}
return 0;
}
2、 poj3320題意:一本書有P頁,每一頁都一個知識點,求去最少的連續頁數覆蓋所有的知識點。
分析:和上面的題一樣的思路,如果一個區間的子區間滿足條件,那麼在區間推進到該處時,右端點會固定,左端點會向右移動到其子區間,且其子區間會是更短的,只是需要存儲所選取的區間的知識點的數量,那麼使用map進行映射以快速判斷是否所選取的頁數是否覆蓋了所有的知識點。
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<map>
#include<set>
#include<string>
#define MAX 100010
#define INF 0x3f3f3f3f
using namespace std;
int a[MAX];
map<int,int>cnt;
set<int>t;
int p,ans=INF,l,r,sum;
int main ()
{
int i;
scanf("%d",&p);
for(i=0;i<p;i++)
{
scanf("%d",&a[i]);
t.insert(a[i]);
}
int num=t.size();
while(1)
{
while(l<p&&sum<num)
if(cnt[a[r++]]++==0)//如果這個知識點沒有選過,就選取它並標記。
sum++;
if(sum<num)
break;
ans=min(ans,r-l);
if(--cnt[a[l++]]==0)
sum--;
/*如果左端點這個知識點已經選過了,那麼就把它踢出去,知識點的個數要減一,之後再繼續循環,如果沒有選擇過那麼就使左端點向右,已選知識點數量不變*/
}
printf("%d\n",ans);
return 0;
}