BZOJ3190 [JLOI2013]赛车(单调栈+半平面交)

【题解】

数形结合思考:
画出v-t图像,若一条直线在一、四象限有不被覆盖的部分,它代表的车就可以领跑 
将直线按他们的斜率从小到大排序后,要维护一个下凸壳,由于一条新加入的直线,能覆盖的左边的直线是从右向左单调的,所以用单调栈来维护 

需要注意的细节:
1. 要在y轴右边做半平面交,我加入了一条过原点,斜率负无穷大的直线,覆盖掉了所有直线的左边一半 
2. 平行线无法算交点,重叠的直线都要算,要特殊判断 


【代码】

#include<stdio.h>
#include<stdlib.h>
#define eps 1e-13
double b[10005],k[10005];
int num[10005],ans[10005],sta[10005];
void jh(int* a,int* b)
{
	int t=*a;
	*a=*b;
	*b=t;
}
void jh(double* a,double* b)
{
	double t=*a;
	*a=*b;
	*b=t;
}
void kp(int low,int high)
{
	int i=low,j=high;
	double mk=k[(i+j)/2],mb=b[(i+j)/2];
	while(i<j)
	{
		while( k[i]<mk || (k[i]==mk&&b[i]<mb) ) i++;
		while( k[j]>mk || (k[j]==mk&&b[j]>mb) ) j--;
		if(i<=j)
		{
			jh(&k[i],&k[j]);
			jh(&b[i],&b[j]);
			jh(&num[i],&num[j]);
			i++;
			j--;
		}
	}
	if(j>low) kp(low,j);
	if(i<high) kp(i,high);
}
void kpans(int low,int high)
{
	int i=low,j=high,mid=ans[(i+j)/2];
	while(i<j)
	{
		while(ans[i]<mid) i++;
		while(ans[j]>mid) j--;
		if(i<=j)
		{
			jh(&ans[i],&ans[j]);
			i++;
			j--;
		}
	}
	if(j>low) kpans(low,j);
	if(i<high) kpans(i,high);
}
double getx(double k1,double b1,double k2,double b2)
{
	return (b2-b1)/(k1-k2);
}
int main()
{
	int n,i,j,top=0;
	scanf("%d",&n);
	for(i=1;i<=n;i++)
		scanf("%lf",&b[i]);
	for(i=1;i<=n;i++)
		scanf("%lf",&k[i]);
	for(i=1;i<=n;i++)
		num[i]=i;
	n++;//加入直线y=0
	k[n]=-1/eps;
	kp(1,n);
	for(i=1;i<=n;i++)
	{
		if(i>1&&k[i]==k[i-1])
		{
			if(b[i]==b[i-1])
			{
				sta[++top]=i;
				continue;
			}
			else
			{
				j=i-1;
				while(j>=1&&sta[top]==j) top--;
			}
		}
		while( top>1 && getx(k[i],b[i],k[sta[top]],b[sta[top]])-getx(k[sta[top-1]],b[sta[top-1]],k[sta[top]],b[sta[top]])<=-eps ) top--;
		sta[++top]=i;
	}
	for(i=1;i<=top;i++)
		ans[i]=num[sta[i]];
	kpans(1,top);
	printf("%d\n",top-1);
	for(i=2;i<=top;i++)
	{
		if(ans[i]!=0) printf("%d",ans[i]);
		if(i<top) printf(" ");
	}
	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,这是一道经典的单调栈问题。题目描述如下: 有 $n$ 个湖,第 $i$ 个湖有一个高度 $h_i$。现在要在这些湖之间挖一些沟渠,使得相邻的湖之间的高度差不超过 $d$。请问最少需要挖多少个沟渠。 这是一道单调栈的典型应用题。我们可以从左到右遍历湖的高度,同时使用一个单调栈来维护之前所有湖的高度。具体来说,我们维护一个单调递增的栈,栈中存储的是湖的下标。假设当前遍历到第 $i$ 个湖,我们需要在之前的湖中找到一个高度最接近 $h_i$ 且高度不超过 $h_i-d$ 的湖,然后从这个湖到第 $i$ 个湖之间挖一条沟渠。具体的实现可以参考下面的代码: ```c++ #include <cstdio> #include <stack> using namespace std; const int N = 100010; int n, d; int h[N]; stack<int> stk; int main() { scanf("%d%d", &n, &d); for (int i = 1; i <= n; i++) scanf("%d", &h[i]); int ans = 0; for (int i = 1; i <= n; i++) { while (!stk.empty() && h[stk.top()] <= h[i] - d) stk.pop(); if (!stk.empty()) ans++; stk.push(i); } printf("%d\n", ans); return 0; } ``` 这里的关键在于,当我们遍历到第 $i$ 个湖时,所有比 $h_i-d$ 小的湖都可以被舍弃,因为它们不可能成为第 $i$ 个湖的前驱。因此,我们可以不断地从栈顶弹出比 $h_i-d$ 小的湖,直到栈顶的湖高度大于 $h_i-d$,然后将 $i$ 入栈。这样,栈中存储的就是当前 $h_i$ 左边所有高度不超过 $h_i-d$ 的湖,栈顶元素就是最靠近 $h_i$ 且高度不超过 $h_i-d$ 的湖。如果栈不为空,说明找到了一个前驱湖,答案加一。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值