尺取法
尺取法:适用求单调区间满足条件的最小区间,时间复杂度O(n)。
延伸右区间,满足条件缩小左区间,得到符合条件的解进而筛选答案
例题一
POJ 3061
Description
A sequence of N positive integers (10 < N < 100 000), each of them less than or equal 10000, and a positive integer S (S < 100 000 000) are given. Write a program to find the minimal length of the subsequence of consecutive elements of the sequence, the sum of which is greater than or equal to S.
Input
The first line is the number of test cases. For each test case the program has to read the numbers N and S, separated by an interval, from the first line. The numbers of the sequence are given in the second line of the test case, separated by intervals. The input will finish with the end of file.
Output
For each the case the program has to print the result on separate line of the output file.if no answer, print 0.
Sample Input
2
10 15
5 1 3 5 10 7 4 9 2 8
5 11
1 2 3 4 5
Sample Output
2
3
题意:给定序列n中,子序列和大于或等于s的最短序列。
#include <stdio.h>
const int MAXN = 1e5 + 10;
int a[MAXN];
int min(int a, int b)
{
return a < b ? a : b;
}
int main()
{
//n个元素组成的数组中找出最短子序列和大于等于s
//尺取法,从起点开始,延伸右区间,直到满足条件,缩短左区间,得到备选答案区间。
int t, n, s, l, r, sum, x, y;
scanf("%d", &t);
while (t--)
{
sum = 0;
scanf("%d%d", &n, &s);
int minlen = n;
//输入数组数据
for (int i = 0; i < n; ++i)
{
scanf("%d", &a[i]);
}
//尺取,left = 0, right = 0
for (l = 0, r = 0; r < n; ++r)
{
//延伸右区间
sum += a[r];
//判断是否满足条件
if (sum < s)
continue;
//满足条件缩小左区间
while (sum - a[l] >= s && l < r)
sum -= a[l++];
//筛选答案
minlen = min(minlen, r-l+1);
}
printf("%d\n", minlen == n ? 0 : minlen);
}
}
例题二
NEFUOJ 1813
难点在于尺取法条件满足的判断,满足的条件为:有26个字符,且每个至少出现一次。
延伸右区间来满足26个字母,缩短左区间来减少相同字符出现的最少次数。
#include <string.h>
#include <stdio.h>
const int MAXN = 1e6 + 10;
char a[MAXN];
int word[300];
int min(int a, int b)
{
return a < b ? a : b;
}
int main()
{
scanf("%s", a);
int len = strlen(a);
int l, r, wordnum = 0, minlen = len;
//尺取法
for (l = 0, r = 0; r < len; ++r)
{
//延伸右区间
if (++word[a[r]] == 1) wordnum++;
//判断是否满足条件
if (wordnum < 26) continue;
//缩短左区间
while (word[a[l]] > 1 && l < r) word[a[l++]] -=1;
//筛选答案
minlen = min(minlen, r-l+1);
}
printf("%d\n", minlen);
return 0;
}
例题三
POJ 2739
构造素数表+尺取法
#include <cstdio>
#include <cmath>
const int MAXN = 10000 + 5;
int prime[MAXN];
int main()
{
bool is_prime;
int cnt = 0;
//构造素数表
for (int i = 2; i < MAXN; ++i)
{
is_prime = true;
for (int j = 2; j <= sqrt(i); ++j)
{
if (i % j == 0)
{
is_prime = false;
break;
}
}
if (is_prime)
{
prime[cnt++] = i;
}
}
int n;
while (~scanf("%d", &n) && n)
{
int l, r, sum = 0, ans = 0;
for (l = r = 0; r < cnt; ++r)
{
//延伸右区间
sum += prime[r];
if (sum < n)
{
continue;
}
//满足条件,缩小左区间
while (sum - prime[l] >= n)
{
sum -= prime[l++];
}
if (sum == n)
{
ans++;
}
}
printf("%d\n", ans);
}
return 0;
}
例题四
POJ 3320
取最短连续的包含所有知识点的序列。由于知识点最大为1e9,在遍历记录知识点数时不能使用普通数组的方式,普通数组最大开到1e7,所以不能使用数组来计算知识点总数,而使用map。
#include <cstdio>
#include <algorithm>
#include <map>
using namespace std;
const int MAXN = 1e6 + 10;
int a[MAXN];
map<int, int>vis;
int main()
{
int n;
scanf("%d", &n);
int sum = 0;
for (int i = 0; i < n; ++i)
{
scanf("%d", &a[i]);
vis[a[i]]++;
//计算知识点总数
if (vis[a[i]] == 1)
sum++;
}
//清空map
vis.clear();
int minlen = n;
int num = 0;
int l, r;
for (l = 0, r = 0; r < n; ++r)
{
//延伸右区间
if (++vis[a[r]] == 1) num++;
//判断是否符合条件
if (num < sum) continue;
//缩短左区间
while (vis[a[l]] > 1 && l < r) vis[a[l++]] -= 1;
//筛选答案
minlen = min(minlen, r-l+1);
}
printf("%d\n", minlen);
return 0;
}
例题五
POJ 2566
由于原区间非单调无法直接使用尺取法,构造新的单调区间,构造原区间[0, idx]元素和排序后成为新的单调区间,用pair存储元素和和idx。构造单调区间便可以使用尺取法,。
p[r].first - p[l].first
也就是[p[r].second, p[l].second]
区间的元素和,与t进行比较,由于每一个差值都是正数,且递增。这里需要仔细体会巧妙之处。
延伸右区间,当差值大于t时,缩小左区间,差值等于t时跳出循环。
#include <cstdio>
#include <cmath>
#include <climits>
#include <algorithm>
#include <map>
using namespace std;
const int MAXN = 1e5 + 10;
pair<int, int> p[MAXN];
int main()
{
int n, k, t;
while (~scanf("%d%d", &n, &k))
{
if (n == 0 && k == 0)
{
break;
}
int x, sum = 0;
p[0] = make_pair(0, 0);// 这个细节十分重要,没有(0, 0)无法得到离0最近的单元素区间
for (int i = 1; i <= n; ++i)
{
scanf("%d", &x);
sum += x;
p[i] = make_pair(sum, i);
}
sort(p, p + n + 1);//排序构造单调区间
for (int i = 0; i < k; ++i)
{
scanf("%d", &t);
int dif = INT_MAX, l, r, ans, low, up;
sum = 0;
for (l = 0, r = 1; r <= n && dif; )//当与t的差值dif为0跳出循环
{
sum = p[r].first - p[l].first;//计算每一段区间的元素和
if (abs(sum - t) <= dif)
{
dif = abs(sum - t);
ans = sum;
low = p[l].second;
up = p[r].second;
}
if (sum < t)//延伸右区间
{
r++;
}
if (sum > t)//缩小左区间
{
l++;
}
if (r == l)//r必须比l大
{
r++;
}
}
if (low > up) swap(low, up);
printf("%d %d %d\n", ans, low + 1, up);
}
}
}