尺取法
作者:互联网
目录
尺取法
1. 算法分析
尺取法: 尺取法通常是对数组保存一对下标,即所选取的区间的左右端点,然后根据实际情况不断地推进区间左右端点以得出答案。尺取法通常可以把一个O(n^2^)的算法利用特殊性质优化到O(n)的复杂度。
for example:
给定一个数列a[n]和一个S, 要求找出连续的一段区间,使得区间和大于等于S,打印这个区间长度的最小值。
- O(n^2^)算法:前缀和预处理,然后O(n^2^)枚举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;
}
3. 例题
Poj3061 Subsequence
题意: 给定一个数列a[n]和一个S, 要求找出连续的一段区间,使得区间和大于等于S,打印这个区间长度的最小值。
题解: 尺取模板题,具体见代码,思路见1.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;
}
poj3320Jessica's Reading Problem
题意: 给定一个n,然后给定n个数字(可能重复),要求取得最小的连续区间,使得能够取到这n个数字中所有的数字。n <= 10^6^
题解: 和上题类似,先while循环判断是否已经取到所有出现的数字,然后收缩左端点。尺取法处理即可。
#include <iostream>
#include <cstdio>
#include <set>
#include <map>
using namespace std;
int const N = 1e6 + 10, INF = 1e9 + 10;
set<int> s;
int a[N], n;
map<int, int> cnt;
int main() {
cin >> n;
for (int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
s.insert(a[i]);
}
int l = 1, r = 1, res = INF, sum = 0, S = s.size(); // S为需要达到的数目,sum记录当前总的数目
while (1) {
while (r <= n && sum < S) {
if (cnt[a[r++]]++ == 0) sum++; // 只有cnt中没出现过才能使得sum++
}
if (sum < S) break;
res = min(res, r - l); // 更新
if (--cnt[a[l++]] == 0) sum--; // l右移使得cnt[a[l]]为时才需要sum--
}
cout << res << endl;
return 0;
}
标签:10,int,res,sum,取法,区间,include 来源: https://www.cnblogs.com/spciay/p/13546588.html