尺取法
1. 算法分析
尺取法: 尺取法通常是对数组保存一对下标,即所选取的区间的左右端点,然后根据实际情况不断地推进区间左右端点以得出答案。尺取法通常可以把一个O(n2)的算法利用特殊性质优化到O(n)的复杂度。之所以能够使用尺取法,是因为元素的前缀和具有单调性,才能使得双指针不会回退。
for example:
给定一个数列a[n]和一个S, 要求找出连续的一段区间,使得区间和大于等于S,打印这个区间长度的最小值。
- O(n2)算法:前缀和预处理,然后O(n2)枚举l和r,每次O(1)计算sum[r] - sum[l]=K的值,然后取得K >= S且长度最小的那个。
- 尺取优化:数学性质:对于l1, r1 和l2, r2,如果l1 < l2, 那么当sum[r2] - sum[l2] >= S,则sum[r1] - sum[l1] >= S且r1 <= r2。由这个性质可以知道当l单调增加时,r也是单调不减的。因此利用这个可以优化算法。具体处理:l=1,r=1,然后不断将r向右移动,直到sum[r] - sum[l] >= S。然后右移l同时满足sum[r] - sum[l] >= S。更新一次区间长度。不断重复这个过程移动r和l,不断更新区间长度。
尺取流程
n = 5, S = 11
a[i]: 1 2 3 4 5
一开始:l = 1, r = 1, sum = 0
然后r不断右移:r = 5, sum = 15
右移l:l = 2, sum=15-1=14>=S
继续左移l直到:l = 3, sum = 12
此时找到最小区间长度为2。
2. 板子
2.1 一维尺取
#include <iostream>
#include <cstdio>
using namespace std;
int const N = 1e5 + 10, INF = 1e9 + 10;
int a[N], n, S, T;
int main() {
cin >> T;
while (T--) {
cin >> n >> S;
for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
int l = 1, r = 1, sum = 0, res = INF; // 初始化左、右边界、区间和
while (1) {
while (r <= n && sum < S) sum += a[r++]; // 计算区间和
if (sum < S) break; // 如果小于S说明找不到>=S的情况,直接break
res = min(res, r - l); // 更新区间
sum -= a[l++]; // 右移l
}
if (res == INF) res = 0;
cout << res << endl;
}
return 0;
}
2.2 二维尺取
#include <bits/stdc++.h>
using namespace std;
int const N = 305, INF = 1e9 + 7;
int r, c, kk, sum[N][N];
char mp[N][N];
int main() {
while (scanf("%d%d%d", &r, &c, &kk