P2698 [USACO12MAR] Flowerpot S 题解

P2698 [USACO12MAR] Flowerpot S

简化题意

有一些水滴,在一个平面直角坐标系上。第 \(i\) 个水滴的坐标为 \((x_i, y_i)\)。水滴会以 \(1\) 单位长度每秒的速度落下,直到落到横轴上。

我们希望在横轴上放置一个花盆,使它接到的第一滴水和最后一滴水的时间差至少为 \(D\) 秒。

求出花盆的最小长度。

解题思路

这题的形式化题意就是:一个坐标系,上面有 \(N\) 个点,我们要求两个点,使它们的纵坐标差至少为 \(D\),且最小化它们的横坐标差。

有的人肯定会问我是怎么得出这个形式化题意的,我就来详细分析一下:
首先,我们容易发现一点,一滴水落到地上(或花盆里)的时间等于它的纵坐标。这点大家应该都能看懂吧。
然后,既然要求的是花盆的最小长度,那么我们发现如果两滴水可以满足需求的话,我们是没必要让花盆变得很长,考虑让水滴分别落在花盆的边缘上就行了,这个长度就是两个水滴横坐标的差值。
现在,我们就可以理解上面的形式化题意了吧。
接下来,我们就要考虑怎么解题了。

首先容易想到 \(O(n ^ 2)\) 的暴力,但是肯定过不了。事实上,我自己做的时候也被卡在这一步了……

接下来,我们思考这题的特点:

  • 接到水滴的花盆可以视作一个区间。
  • 我们要求这个区间里的最大值与最小值,并考虑是否能做到最大值减最小值大于 \(D\)。

这是不是让我们想到了滑动窗口?想一想:求一个区间的最大值和最小值……但是这题和它的区别是:我们不知道区间的长度。
因此,这里我们想到了单调队列的解法(因为我们需要考虑区间长度的变化)。
形象化的,就是一个区间会滑动(变化下标),还会按照是否满足解的情况来调整区间大小(扩大)。

具体的操作就是:

  • 创建两个单调队列,第一个记录最大值,第二个记录最小值。
  • 首先,我们把两个队列超出区间范围的元素都弹出队列。
  • 然后,我们判断:是否目前满足两个队首相减大于等于 \(D\)(即是否已经有解)。
  • 如果没有,我们尝试不断将区间向右扩大,并同时记录最大、最小值。
  • 跳出循环后记录答案即可。

有了操作思路,我们的代码就能实现了。

代码部分

#include <bits/stdc++.h>
using namespace std;
struct node
{
	int x, y;
}a[100010];
inline bool cmp(node a, node b)
{
	return a.x < b.x;
}
int n, d, ans = INT_MAX;
int q1[100010], q2[100010];
int main()
{
	cin >> n >> d;
	for(int i = 1;i <= n;i ++)
		cin >> a[i].x >> a[i].y;
	sort(a + 1, a + 1 + n, cmp);//坐标按横坐标值升序排序 
	int tail1 = 0, head1 = 1, tail2 = 0, head2 = 1, r = 1;
	q1[1] = 1, q2[1] = 1;//初始化 
	for(int l = 1;l <= n;l ++)
	{
		while(head1 <= tail1 && q1[head1] < l)//弹出超出范围的数
			head1 ++;
		while(head2 <= tail2 && q2[head2] < l)
			head2 ++;
		 
		while(a[q1[head1]].y - a[q2[head2]].y < d && r < n)
		{
			r ++;//扩大下标 
			while(tail1 >= head1 && a[q1[tail1]].y < a[r].y)//进行新的元素的插入 
				tail1 --;
			q1[++ tail1] = r;
			while(tail2 >= head2 && a[q2[tail2]].y > a[r].y)
				tail2 --; 
			q2[++ tail2] = r;
		}
		if(a[q1[head1]].y - a[q2[head2]].y >= d)//记录答案(需要满足有解) 
			ans = min(ans, a[r].x - a[l].x);
	}
	if(ans == INT_MAX)
		ans = -1;
	cout << ans << endl;
	return 0;
}

posted on 2026-03-24 22:15  Leo_29  阅读(36)  评论(0)    收藏  举报

导航