尺取法简介
尺取法:顾名思义,像尺子一样取一段,借用挑战书上面的话说,尺取法通常是对数组保存一对下标,即所选取的区间的左右端点,然后根据实际情况不断地推进区间左右端点以得出答案。之所以需要掌握这个技巧,是因为尺取法比直接暴力枚举区间效率高很多,尤其是数据量大的。
那么,用”尺取法“做上面这道题思路应该是这样的:
其实,这种方法很类似于蚯蚓的蠕动。
1)用一对脚标 i , j i, j i,j。最开始都指向第一个元素。
2)如果区间 i i i到 j j j之和比 s s s小,就让 j j j往后挪一位,并把 s u m sum sum的值加上这个新元素。相当于蚯蚓的头向前伸了一下。
3)如果区间 i i i到 j j j之和比 s s s大,就让 s u m sum sum减掉第一个元素。相当于蚯蚓的尾巴向前缩了一下。
4)如果i到j之和刚好等于 s s s,则输入。
上一道题来熟悉一下
有一个长度为NN的正整数序列 ( 10 < N < 100 , 000 ) (10 < N < 100,000) (10<N<100,000),每一个数字都小于等于10000,再给定一个正整数 S ( S < 100 , 000 , 000 ) S (S < 100,000,000) S(S<100,000,000),试求一个连续子序列,使得该序列的数字之和大于或等于 S S S,并且要求该子序列尽量短。
tips1:这道题首先需要想到的是使用 s u m [ i ] sum[i] sum[i]数组表示前 i i i个元素的和,对于这种求序列和的问题,很多时候都会使用到这种数组。
暴力–代码不一定对
最简单的方法,二重循环遍历
for (ll i = 1; i <= n; i++) {
for (ll j = i + 1; j <= n; j++) {
if (sum[j] - sum[i] >= s)
ans = min(ans, j - i);
}
}
显然暴力过于low,如果提交肯定会TLE,想一个办法改进一下。
二分搜索
二分搜索简介,根据简介的内容,我们只需要
1)将子序列的长度作为搜索的对象
2)对二分搜索的每个
m
i
d
mid
mid值,判断该长度的子序列能否满足和大于
S
S
S。
3)由于是求最短长度,如果子序列满足条件,那么
r
=
m
i
d
r=mid
r=mid,否则
l
=
m
i
d
l=mid
l=mid。
直接使用STL的
l
o
w
e
r
_
b
o
u
n
d
lower\_bound
lower_bound函数代码将更加简洁.
最后介绍尺取法
尺取法通常会用于处理区间问题,
1、什么时候可以用尺取法?
尺取法通常适用于选取区间有一定规律,或者说所选取的区间有一定的变化趋势的情况,比如单调性的区间就是经常用到尺取法的经典区间。
2、用尺取法的时候要注意什么问题?
1、 什么情况下能使用尺取法?
2、如何推进区间的端点?
3、如何利用当前区间更新结果值?
4、何时结束区间的枚举?
对于该题而言,我们依次回答上面四个问题
1、虽然对原数组而言并不具有什么单调性,但是
s
u
m
sum
sum数组却具有单调性。而具有单调性的数组一般是适用尺取法的。
2、对每个左端点,给一个 w h i l e while while循环,只要有 s u m < S sum<S sum<S,就不断的将右端点右移。
3、循环结束后,如果 s u m [ r ] − s u m [ l ] > s sum[r]-sum[l]>s sum[r]−sum[l]>s,说明 r − l r-l r−l可以被用于更新
4、如果(2)中直到循环结束的 s u m [ r ] − s u m [ l ] sum[r]-sum[l] sum[r]−sum[l]值也不能够满足要求,此时就可以break了,因为左端点右移得到 s u m [ r ] − s u m [ l ] sum[r]-sum[l] sum[r]−sum[l]值只会更小、
令
s
=
15
s=15
s=15,给定数据15 5 1 3 5 10 7 4 9 2 8,区间变化如下
再来一道题
该问题也就是求一个最小的区间,改区间的数字覆盖所有数字的种类。
1)首先对每一个左端点
s
s
s,一个
w
h
i
l
e
while
while循环,求出能使得
s
−
−
t
s--t
s−−t的知识点全覆盖的最小的右端点
t
t
t,如果该
s
s
s,不能有一个
t
t
t使得
s
−
−
t
s--t
s−−t覆盖所有知识点,就可以退出循环了。
2)如果可以完全覆盖,那么可以比较
s
−
t
s-t
s−t的值是否比之前的answer更小,更新结果值。右移
s
s
s一位,将
s
s
s对应的知识点-1。