把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【BZOJ1007】[HNOI2008] 水平可见直线(几何)

点此看题面

大致题意: 给定\(n\)条直线,求从上往下看所有没被完全覆盖的直线编号。

前言

叶老师给的题单上几何部分一直空着。。。

最近还是打算抽些时间补下比较薄弱的地方,比如之前尝试去练习的博弈论(五花八门,感觉根本没什么算法可言)、网络流(玄学建图,按照我的做题速度这辈子都掌握不了套路),相信对于几何肯定最终也是差不多的结果。。。

最终吧,不会的仍旧不会,会的也变成了不会,只是一个由弱变得更弱的过程。

大致思路

首先有一个显然的结论:对于一条直线,如果有一条斜率等于它的直线截距比它大,那么这条直线肯定被覆盖了。

否则,当且仅当存在一条斜率小于它的直线与它的交点在一条斜率大于它的直线与它的交点右边(或重合),这条直线才会被覆盖。如图就是一个例子:

其中,\(k_{blue}<k_{red}<k_{green}\),而红蓝交点在红绿交点右边,所以红色直线就被覆盖了。

然后我们立刻就能得到一个想法:我们枚举每条直线,求出斜率小于它的直线与它最右边的交点和斜率大于它的直线与它最左边的交点,这样就很好验证了。

然而,我们该如何在\(O(logn)\)\(O(\sqrt n)\)的时间复杂度内求出这两个交点呢?

我想了半天好像也没能有什么想法,只好换了个思路。

考虑我们将直线按斜率从小到大排序,然后枚举每一条直线,它都必然是当前斜率最大的直线,是不可能被覆盖的。

因此,我们只需要考虑之前我们所认为的答案中不合法的直线,即与当前直线交点在与斜率小于它的直线最右边的交点的左边的直线。

然后我们发现,一条直线与斜率小于它的直线最右边的交点,其实就是与答案序列中上一条直线的交点。

可以按照下面这张图理解一下:

其中\(k_{green}<k_{blue}<k_{red}\)

根据左图,显然红蓝交点在红绿交点右侧,符合我们上面的结论。

根据右图,虽然红蓝交点在红绿交点左侧,但我们发现此时蓝色直线会被覆盖,因此这种情况是不存在的。

最终,整理一下思路,然后发现只要写个单调栈,这道题就做完了。。。

代码

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 50000
using namespace std;
int n,p[N+5];
struct line
{
	int k,b,id;I line(CI x=0,CI y=0,CI p=0):k(x),b(y),id(p){}
	I bool operator < (Con line& o) Con {return k^o.k?k<o.k:b>o.b;}//按斜率排序,注意斜率相同时让截距大的排前面
}s[N+5],S[N+5];
int main()
{
	RI i;for(scanf("%d",&n),i=1;i<=n;++i) scanf("%d%d",&s[i].k,&s[i].b),s[i].id=i;
	RI T=0;for(sort(s+1,s+n+1),i=1;i<=n;++i)
	{
		if(T&&s[i].k==S[T].k) continue;//斜率相同时,截距小的会被截距大的覆盖,直接跳过
		W(T>1&&1LL*(S[T-1].b-S[T].b)*(s[i].k-S[T].k)>=1LL*(S[T].b-s[i].b)*(S[T].k-S[T-1].k)) --T;
		//判断交点左右顺序,这里为防止被卡精度改除法为乘法
		S[++T]=s[i];//加入单调栈
	}
	for(i=1;i<=T;++i) p[S[i].id]=1;for(i=1;i<=n;++i) p[i]&&printf("%d ",i);return 0;//输出答案
}
posted @ 2020-05-25 09:13  TheLostWeak  阅读(146)  评论(0编辑  收藏  举报