返回的推进区间开头和结尾,求满足条件的最小区间的方法称为尺取法。尺取法,顾名思义,像尺子一样,一块一块的截取。用尺取法来优化,使复杂度降为了O(n)。
整个过程分为四步:
1.初始化左右端点
2.不断扩大右端点,直到满足条件
3.如果第二步中无法满足条件,则终止,否则更新结果
4.将左端点扩大1,然后回到第二步
下面贴代码(本人蒟蒻勿喷QwQ
<span style="font-size:18px;"><</span><span style="font-size:14px;">span style="color:#000000;"><strong>// POJ 3061
#include<iostream>
#include<cstdio>
using namespace std;
const int maxn=100010;
int num[maxn];
int n,S;
int main()
{
int t;scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&S);
for(int i = 1; i <= n; i ++)
{
scanf("%d",&num[i]);
}
int sum = 0,s = 1,e = 1;
int ans = n + 1;
for(;;)
{
while(e <= n && sum < S)
{
sum += num[e++];
}
if(sum < S)
{
break;
}
ans = min(ans ,e - s);
sum -= num[s++];
}
if(ans == n + 1)
cout<<0<<endl;
else
cout<<ans<<endl;
}
return 0;}</strong></span></span>
<span style="font-size:14px;"><strong>//POJ 2566
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 100000 + 5;
const int inf = 0x3f3f3f3f;
int A[maxn],N,K;
struct Sum
{
int s,p;
inline bool operator < (const Sum &b) const { return s < b.s; }
} S[maxn];
inline void Qry(int x)
{
int l = 0,r = 1,mn = inf;
int rc,rc_l,rc_r;
while(l <= N && r <= N)
{
int t = S[r].s - S[l].s;
if(abs(t - x) < mn)
{
rc_l = S[l].p,rc_r = S[r].p;
rc = t; mn = abs(t - x);
}
if(t > x) ++ l;
else if(t < x) ++ r;
else break;
if(l == r) ++ r;
}
if(rc_l > rc_r) swap(rc_l,rc_r);
printf("%d %d %d\n",rc,rc_l + 1,rc_r);
}
int main()
{
while(scanf("%d%d",&N,&K), N || K)
{
S[0].s = 0; S[0].p = 0;
for(int i = 1;i <= N;i ++) scanf("%d",A + i),S[i].s = S[i - 1].s + A[i],S[i].p = i;
sort(S,S + 1 + N);
while(K --)
{
int T; scanf("%d",&T);
Qry(T);
}
}
return 0;
}</strong></span>
首先 第一题保证每个数不是负数,很简单的用尺取法秒过,但要注意无解输出0的情况。
其次第二题,不能保证每个数是否大于0,不能满足单调;
但是,我们要求的是数列和的绝对值,就可以做点什么事了。
绝对值满足性质:| sum[i] - sum[j] | == | sum[j] - sum[i] |
因此,我们就能改变前缀和的顺序了;
然后,就可以按照3061的尺取法思路求解了。
嗯嗯,就是这样。
尺取法大致就是这样,(雾