http://poj.org/problem?id=3061
给定长度为n的整数数列以及整数S,求出总和不小于S的连续子序列的长度的最小值,如果解 不存在,输出0.
如果用二分法:
先求出sum[i],从第1个数到第i个数的区间和,每次固定一个开始查找的起点sum[i], 然后采用二分查找找到 sum[i] + S 的位置,区间长度即为(末位置-(起始位置-1)),用ans保存过程中区间的最小值。时间复杂度 O(nlogn)。
但如果用尺取法会将复杂度大大降低:
反复地推进区间的开头和末尾,来求满足条件的最小区间的方法称为尺取法。
主要思想为:当a1, a2 , a3 满足和>=S,得到一个区间长度3,那么去掉开头a1, 剩下 a2,a3,判断是否满足>=S,如果满足,那么区间长度更新,如果不满足,那么尾部向后拓展,判断a2,a3,a4是否满足条件。重复这样的操作。
时间复杂度 O(n)。
对尺取法的深入理解:
当一个区间满足条件时,那么去掉区间开头第一个数,得到新区间,判断新区间是否满足条件,如果不满足条件,那么区间末尾向后扩展,直到满足条件为之,这样就得到了许多满足条件的区间,再根据题意要求什么,就可以在这些区间中进行选择,比如区间最长,区间最短什么的。
Source Code
Problem: 3061 User: liangrx06
Memory: 632K Time: 63MS
Language: C++ Result: Accepted
Source Code
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 1e5;
int main(void)
{
int t, n, s;
int a[N];
cin >> t;
while (t--) {
cin >> n >> s;
for (int i = 0; i < n; i ++)
scanf("%d", &a[i]);
int ans = n+1;
int l = 0, r = 0, sum = 0;
while (true) {
while (sum < s && r < n)
sum += a[r++];
if (sum < s) break;
ans = min(ans, r-l);
sum -= a[l++];
}
printf("%d\n", ans%(n+1));
}
return 0;
}
http://poj.org/problem?id=3320
某人读一本书,要看完所有的知识点,这本书共有P页,第i页恰好有一个知识点ai,(每一个知识点都有一个整数编号)。全书同一个知识点可能会被提到多次,他希望阅读其中一些连续的页把所有知识点都读到,给定每页所读到的知识点,求最少的阅读页数。
和上一题一样,也是尺取法的应用。
假设从某一页s开始阅读,为了覆盖所有的知识点读到t页,这样的话如果从s+1开始阅读,那么必须读到t’>=t位置,故可以用尺取法。
预处理时要用set来统计所有知识点。
尺取法循环过程中,对每个知识点用map来统计次数,增加后一项要把对应的知识点的编号次数+1,取出前一项要把对应的知识点的编号次数-1。用cnt统计覆盖的知识点数目,知识点次数由0变为1时cnt++,反之则cnt–。
Source Code
Problem: 3320 User: liangrx06
Memory: 5116K Time: 532MS
Language: C++ Result: Accepted
Source Code
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <set>
#include <map>
using namespace std;
const int N = 1e6;
int main(void)
{
int n;
int a[N];
set <int> ideas;
map <int, int> num;
cin >> n;
for (int i = 0; i < n; i ++) {
scanf("%d", &a[i]);
ideas.insert(a[i]);
num[a[i]] = 0;
}
int s = ideas.size();
int l = 0, r = 0, cnt = 0;
int ans = n;
while (true) {
while (r < n && cnt < s) {
if (num[a[r]] == 0) cnt++;
num[a[r++]] ++;
}
if (cnt < s) break;
ans = min(ans, r-l);
if (num[a[l]] == 1) cnt--;
num[a[l++]] --;
}
printf("%d\n", ans);
return 0;
}
http://poj.org/problem?id=2739
给定一个10000以内的数字,判断这个数字是否可以由几个连续的素数(例如:2,3,5,7…)相加得到,并且给出这个数可以有几组这样的解。
输入:每行一个数字,0为退出
输出:每行一个数字,对应输入的每个数字的解的组数。
预先用素数筛法求出10000以内的素数,按顺序存储。然后用尺取法求解。
Source Code
Problem: 2739 User: liangrx06
Memory: 284K Time: 0MS
Language: C++ Result: Accepted
Source Code
#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;
const int N = 10000;
bool isPrime[N+1];
int prime[N+1];
void initPrime()
{
fill(isPrime, isPrime+N, true);
fill(prime, prime+N, N+1);
isPrime[1] = false;
int cnt = 0;
for (int i = 2; i <= N; i ++) {
if (isPrime[i]) {
prime[cnt++] = i;
if (i > (int)sqrt((double)N)) continue;
for (int j = i*i; j <= N; j += i) {
isPrime[j] = false;
}
}
}
}
int main(void)
{
int n;
initPrime();
while (cin >> n && n) {
int ans = 0;
int l = 0, r = 0, sum = 0;
while (true) {
while (sum < n && prime[r] <= n)
sum += prime[r++];
if (sum < n) break;
if (sum == n) ans++;
sum -= prime[l++];
}
printf("%d\n", ans);
}
return 0;
}
http://poj.org/problem?id=2100
是否存在一段连续的数, 平方相加等于n, 求出方案数并输出方案.
连续子序列的性质通常用尺取法比较合适。另外这个题其实保存序列后不需要重新排序,顺序输出即可。
Source Code
Problem: 2739 User: liangrx06
Memory: 284K Time: 0MS
Language: C++ Result: Accepted
Source Code
#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;
const int N = 10000;
bool isPrime[N+1];
int prime[N+1];
void initPrime()
{
fill(isPrime, isPrime+N, true);
fill(prime, prime+N, N+1);
isPrime[1] = false;
int cnt = 0;
for (int i = 2; i <= N; i ++) {
if (isPrime[i]) {
prime[cnt++] = i;
if (i > (int)sqrt((double)N)) continue;
for (int j = i*i; j <= N; j += i) {
isPrime[j] = false;
}
}
}
}
int main(void)
{
int n;
initPrime();
while (cin >> n && n) {
int ans = 0;
int l = 0, r = 0, sum = 0;
while (true) {
while (sum < n && prime[r] <= n)
sum += prime[r++];
if (sum < n) break;
if (sum == n) ans++;
sum -= prime[l++];
}
printf("%d\n", ans);
}
return 0;
}