尺取法 求满足条件的最小区间

https://vjudge.net/problem/POJ-3061

给n个数 一个s  求总和大于等于s的连续子序列的长度最小值,不存在输0

一开始想暴力: for遍历起点   for(1长度到可以的长度) TLE

上面的想法就是确定   起点  终点

现在通过一个sum【i】=a0+。。。ai-1    这个前缀和    把一段连续子序列表达成两个前缀和的差值

即  as+。。。。at-1 = sum【t】 - sum【start】

因此得到一个式子   sum【t】 - sum【start】>= s

由于sum这个数组 是从小到大的   所以可以二分

转换成sum【t】 >= s + sum【start】            不知道这是不是所谓的   离散化

所以思路:  遍历起点start  找这样一个  t

const int maxn = 1e5 + 100;
int sum[maxn];//sum[i]  a0 +.....ai-1
int n, s;
void solve() {
	//全部加起来 都不能大于等于0  更不用说连续子序列了 肯定没解
	if (sum[n] < s) {
		cout << 0 << endl;
		return;
	}
	int res = n;//该题最大就是全部数 即n长度
	//sum 是前缀和  从小到大的
	//中间判断 如果一旦大于 全部数的和 那肯定找不到
	for (int start = 1; /*start <= n*/sum[start] + s <= sum[n]; ++start) {
		//这里经过了离散化 从sum + start找是因为 终点t肯定在当前start后面
		int t = lower_bound(sum + start, sum + n + 1, sum[start] + s) - sum;
		res = min(res, t - start);//a0....at-1   a0.....a(start-1) 即start。。。t-1  所以t-start就是连续子序列长度
	}
	cout << res << endl;
}
int main() {
	ios;
	int t;
	cin >> t;
	while (t--) {
		int num;
		cin >> n >> s;
		sum[0] = 0;
		for (int i = 0; i < n; ++i) {
			cin >> num;
			sum[i + 1] = sum[i] + num;
		}

		solve();
	}
}

尺取法 O(n)

算法解释在 挑战p148页

取法:顾名思义,像尺子一样取一段,借用挑战书上面的话说,尺取法通常是对数组保存一对下标,即所选取的区间的左右端点,然后根据实际情况不断地推进区间左右端点以得出答案。之所以需要掌握这个技巧,是因为尺取法比直接暴力枚举区间效率高很多,尤其是数据量大的时候,所以尺取法是一种高效的枚举区间的方法,一般用于求取有一定限制的区间个数或最短的区间等等。当然任何技巧都存在其不足的地方,有些情况下尺取法不可行,无法得出正确答案。

使用尺取法时应清楚以下四点:1、 什么情况下能使用尺取法? 2、何时推进区间的端点? 3、如何推进区间的端点? 3、何时结束区间的枚举?

尺取法通常适用于选取区间有一定规律,或者说所选取的区间有一定的变化趋势的情况,通俗地说,在对所选取区间进行判断之后,我们可以明确如何进一步有方向地推进区间端点以求解满足条件的区间,如果已经判断了目前所选取的区间,但却无法确定所要求解的区间如何进一步得到根据其端点得到,那么尺取法便是不可行的。首先,明确题目所需要求解的量之后,区间左右端点一般从最整个数组的起点开始,之后判断区间是否符合条件在根据实际情况变化区间的端点求解答案。 以下是几个经典的使用尺取法的例题,都是从挑战书上引用的。(尺取法通常会需要对某些量进行预处理,以便能在使用时快速地判断。) --------------------- 本文来自 HopeForBetter 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/consciousman/article/details/52348439?utm_source=copy

总结:尺取法的模型便是这样:根据区间的特征交替推进左右端点求解问题,其高效的原因在于避免了大量的无效枚举,其区间枚举都是根据区间特征有方向的枚举,如果胡乱使用尺取法的话会使得枚举量减少,因而很大可能会错误,所以关键的一步是进行问题的分析! --------------------- 本文来自 HopeForBetter 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/consciousman/article/details/52348439?utm_source=copy

const int maxn = 1e5 + 100;
int a[maxn], n, s;

void solve() {
	int sum, start, end, res = n + 1;//用来看无解
	sum = start = end = 0;
	while (1) {
		//end指到尾 或者 当前 sum >= s 发生就退出循环
		while (end < n && sum < s) {
			sum += a[end++];//end会在最后一个加的 后面
		}
		if (sum < s) break;//如果退出循环后 找不到符合条件的 就退出 因为上面已经加到不能再加
		res = min(res, end - start);
		sum -= a[start++];//减去前的 往后移动
	}
	if (res == n + 1)
		res = 0;
	cout << res << endl;
}
int main() {
	ios;
	int t;
	cin >> t;
	while (t--) {
		cin >> n >> s;
		for (int i = 0; i < n; ++i) cin >> a[i];
		solve();
	}
}

codeforce #round509 div2           D-Glider

two point   尺取法

题意:给一些连续区间块   在这里面 飞行员能平滑   不在就会以y = -x + b 直线 下降

让你找一个最长区间【i,j】长度      i点开始跳   j点是落地 

首先:贪心 一定是在每个连续区间块 最左边开始跳的  

因为如果你在区间内跳  反而少了一些水平距离  

在外跳的话  会下落 再到区间的最左边  所以很容易知道这个结论

还有  h只跟  每两个连续区间块之间 的距离有关

for(遍历起点 即每个区间最左边)

   while(加到落地 求过了多少个连续区间块)   。。。。。TLE   每次都求和一遍

所以改用尺取法 

#include<iostream>
#include<algorithm>

using namespace std;
#define inf 0x3f3f3f3f
const int maxn = 2e5 + 100;
int a[maxn], b[maxn], seg[maxn], interval[maxn];
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int n, h;
	while (cin >> n >> h){
		for (int i = 1; i <= n; ++i) {
			cin >> a[i] >> b[i];
			seg[i] = b[i] - a[i];
			interval[i] = a[i] - b[i - 1];//b【0】 = 0
			//seg是1到n   interval[2] 第一个间隔  2到n
		}
		int res = -inf;
		int hh, tmp, start, end;
		hh = tmp = 0;
		start = end = 1;
		while (1){
			//因为seg  1到n  所以要加到n   
			//因为斜率是1  水平距离 就是 下降距离 
			while (end <= n && hh < h){
				tmp += seg[end];
				//interval 2到n
				//如果已经最后一个间距了
				if (end + 1 > n) { 
					end++;
					break;
				}
				//不是最后一个间距就加
				hh += interval[end + 1];
				end++;
			}
			res = max(res, tmp + h);//tmp就是 这一个 连续区间 的seg和  下降距离即 hh + (h - hh)  即h
			if (end == n + 1) break;
			hh -= interval[start + 1];//减前面  尺取法
			tmp -= seg[start++];
			
		}
		cout << res << endl;
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/liangnimahanwei/article/details/82807908