尺取法

反复推进区间的开头与末尾,这样的方法叫做尺取法,求给定长度内的最短区间可以满足某些性质。


POJ3061

题意:

给定长度为 n 的数列整数,以及整数 S ,求出总和不小于 S 的连续子序列的长度的最小值.如果解不存在则输出 0 。

解:

不断的推进首位,每推进一次首位,就往后推近末尾直到区间序列的和大于等于S。

 1 int N, S;
 2 int a[MAXN];
 3 
 4 void solve() {
 5     int res = N + 1;
 6     int s = 1, t = 1, sum = 0;
 7     for (;;) {
 8         while (t <= N && sum < S) {
 9             sum += a[t++];
10         }
11         if (sum < S) break;
12         res = min(t - s, res);
13         sum -= a[s++];
14     }
15     if (res > N) res = 0;
16     printf("%d
", res);
17 }
18 
19 int main() {
20 #ifndef ONLINE_JUDGE
21     freopen("input.txt", "r", stdin);
22 #endif
23     int T = READ();
24     while (T--) {
25         CLR(a);
26         scanf("%d %d", &N, &S);
27         REP(i, 1, N) scanf("%d", &a[i]);
28         solve();
29     }
30     return 0;
31 }

POJ3320

题意:

一本书总共有 P 页,第 i 页有一个知识点 $a_i$ (每个知识点都有一个编号),求最少读连续的多少页就可以掌握所有知识点

解法:

尺取法,用一个单独的变量维护当前读过的区间内的知识数,判断其与知识总数是否相等,若相等则更新答案,否则继续推。

 1 int P;
 2 int a[MAXN];
 3 set<int> all;
 4 
 5 void solve() {
 6     int num = all.size();
 7     int s = 1, t = 1, sum = 0;
 8     int res = P;
 9     map<int, int> count;
10     for (;;) {
11         while (t <= P && sum < num) {
12             if (count[a[t++]]++ == 0) sum++;
13         }
14         if (sum < num) break;
15         res = min(t - s, res);
16         if (--count[a[s++]] == 0) sum--;
17     }
18     printf("%d
", res);
19     return;
20 }
21 
22 int main() {
23 #ifndef ONLINE_JUDGE
24     freopen("input.txt", "r", stdin);
25 #endif
26     scanf("%d", &P);
27     for (int i = 1; i <= P; i++) {
28         scanf("%d", &a[i]);
29         all.insert(a[i]);
30     }
31     solve();
32     return 0;
33 }

POJ2566

题意:

从数列中找出连续序列,使得和的绝对值与目标数之差最小。

多组目标数,数列长度为1e6

解法:

注意是要求和的绝对值,那么我们就可以先把前缀和都算出来,然后用尺取法一直推进即可。

因为是前缀和,所以不存在两个区间没有交集的情况。

代码如下:

 1 int n, m, k;
 2 pair<LL, int> sum[MAXN];
 3 
 4 LL myabs(LL x) { return x >= 0 ? x : -x; }
 5 
 6 void solve() {
 7     while (m--) {
 8         int s = 0, t = 1;
 9         LL ans = LLINF, th, tmpans = LLINF;
10         int l, r;
11         scanf("%lld", &th);
12 
13         while (t <= n) {
14             LL diff = sum[t].first - sum[s].first;
15             if (myabs(diff - th) < tmpans) {
16                 tmpans = myabs(diff - th);
17                 ans = diff;
18                 l = sum[s].second;
19                 r = sum[t].second;
20             }
21             if (diff == th) {
22                 break;
23             } else {
24                 diff > th ? s++ : t++;
25             }
26             if (s == t) t++;
27         }
28         if (l > r) swap(l, r);
29         printf("%lld %d %d
", ans, l + 1, r);
30     }
31     return;
32 }
33 
34 int main() {
35 #ifndef ONLINE_JUDGE
36     freopen("input.txt", "r", stdin);
37 #endif
38     while (scanf("%d %d", &n, &m), n + m) {
39         sum[0].first = 0;
40         sum[0].second = 0;
41         for (int i = 1; i <= n; i++) {
42             LL x;
43             scanf("%lld", &x);
44             sum[i].first = sum[i - 1].first + x;
45             sum[i].second = i;
46         }
47         sort(sum, sum + n + 1);
48         solve();
49     }
50     return 0;
51 }

POJ2739

题意:给定一个数,求这个数可以分解为多少个连续素数的和

解法:

素数打标之后直接尺取法即可,注意这个表的下表是从1开始的

代码如下:

 1 bool visit[10100000];
 2 int prime[10000000];
 3 int T;
 4 
 5 int prime_num = 0;
 6 void generate_prim(int n) {
 7     memset(visit, true, sizeof(visit));
 8     for (int i = 2; i <= n; ++i) {
 9         if (visit[i] == true) prime[++prime_num] = i;
10         for (int j = 1; ((j <= prime_num) && (i * prime[j] <= n)); ++j) {
11             visit[i * prime[j]] = false;
12             if (i % prime[j] == 0) break;
13         }
14     }
15     return;
16 }
17 
18 int main() {
19 #ifndef ONLINE_JUDGE
20     freopen("input.txt", "r", stdin);
21 #endif
22     generate_prim(10000 + 1);
23     while (scanf("%d", &T) && T) {
24         int cnt = 0;
25         int s = 1, t = 1, sum = 0;
26         for (;;) {
27             while (t <= prime_num && sum < T) {
28                 sum += prime[t++];
29             }
30             if (sum < T) break;
31             if (sum == T) cnt++;
32             sum -= prime[s++];
33             if (prime[s] > T) break;
34         }
35         printf("%d
", cnt);
36     }
37     return 0;
38 }

POJ2100

题意:给定一个数,求这个数T( T < 1e14 )可以分解为多少个连续数的平方和,输出方案数与每个方案

解法:T开根号之后正好1e7,O( n )就可以过,所以这里用尺取法即可

 1 LL T;
 2 vector<vector<LL>> ans;
 3 
 4 LL poww(LL num) { return num * num; }
 5 
 6 bool cmp(const vector<LL>& v1, const vector<LL>& v2) {
 7     return v1.size() > v2.size();
 8 }
 9 
10 int main() {
11     while (cin >> T) {
12         // T = 2030;
13         LL s = 1, t = 1;
14         LL sum = 0;
15         LL len = (int)sqrt(T * 1.0) + 1;
16         ans.clear();
17         for (;;) {
18             while (poww(t) <= T && sum < T) {
19                 sum += poww(t++);
20             }
21             if (sum < T) break;
22             if (sum == T) {
23                 vector<LL> v;
24                 for (int i = s; i < t; i++) {
25                     v.push_back(i);
26                 }
27                 ans.push_back(v);
28             }
29             sum -= poww(s++);
30             if (poww(s) > T) break;
31         }
32 
33         sort(ans.begin(), ans.end(), cmp);
34         printf("%d
", ans.size());
35         for (int i = 0; i < ans.size(); i++) {
36             printf("%d", ans[i].size());
37             // sort(ans[i].begin(), ans[i].end());
38             for (int j = 0; j < ans[i].size(); j++) {
39                 printf(" %lld", ans[i][j]);
40             }
41             printf("
");
42         }
43     }
44     return 0;
45 }
原文地址:https://www.cnblogs.com/romaLzhih/p/11619882.html