题意乍一看不是很明确…所以首先解释一下题干。
如上图,蓝色的是各式各样的直线,而我们要求的就是最上面的这一部分包含哪些直线。
就相当于求一个分段函数函数的极值。并且,我们称这些部分的集合为凸壳。
我们一点一点来思考。首先介绍一下单调栈的概念:
如果有一个栈,支持不断弹出以维护单调性,那么就称这个栈叫做单调栈。对单调性的维护方法是因题而异的。由于每个元素至多进入一次,所以其复杂度是
O(n)
的。
我们首先按照斜率从小到大的顺序排序。然后再以此为基准进行下面的思考。至于为什么这样做,下面再来分析。
思考一下就可以想象到,斜率最小和最大的应该是保留的,因为这是最边上的两段凸壳。
首先来看两条线的情况,结果就是红色的一段。显然,两条线都在凸壳内,我们定义交点是
P
点。将这两条线段加入栈中,由于斜率排过序,我们将紫色的称为1,蓝色的称为2。
然后这时加入一条线,同样由于斜率排过序,所以结果应该是两条绿线之一:
上面的绿线与2的交点在
下面的绿线与2的交点在
P
<script type="math/tex" id="MathJax-Element-837">P</script>的右边,那么中间就有一段凸壳并没有被取代,所以我们不弹出,然后加入3号,栈的情况是{1, 2, 3}。
如果斜率的符号发生改变,结果应该是相似的:
保留与否观察交点位置即可。
好像看出了些规律:如果一条线进入之后与上一条线的交点在上一个交点的左边,那么就弹出上一条线;在右边则保留。至于不巧一样的情况,画一个图就可以明白应该是弹出上一条线。
这个弹出和比较的过程应该一直持续到交点产生在右边为止。
至于判断交点,直接联立方程比较横坐标即可。
最后的问题是斜率相同,截距不同的情况。同样画个图yy一下:
如果一组直线斜率相同,并且作为凸壳的一段,那么一定取截距最大的一段,因为会把下面的都遮住。
实现上我们可以从大到小排序,只保留第一条,然后把剩下的都跳过就好辣
附上丑陋的代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
struct node{
int id, A, B;
}line[50003];
int n;
int stack[50003], cnt = 0;
int ans[50003];
bool cmp(node a, node b) {
return a.A == b.A ? a.B > b.B : a.A < b.A;
}
inline double solve(int x, int y) { //解方程
return (line[x].B - line[y].B) * 1.0 / (line[x].A - line[y].A) * 1.0;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cin>>n;
for(int i = 1; i <= n; ++i) {
line[i].id = i;
cin>>line[i].A>>line[i].B;
}
sort(line + 1, line + n + 1, cmp);
for(int i = 1; i <= n; ++i) {
if(i != 1 && line[i].A == line[i - 1].A) continue; // 斜率相等
while(solve(i, stack[cnt]) >= solve(stack[cnt], stack[cnt - 1]) && cnt > 1) {
--cnt;
}
stack[++cnt] = i;
ans[cnt] = line[stack[cnt]].id;
}
sort(ans + 1, ans + cnt + 1);
for(int i = 1; i <= cnt; ++i)
cout<<ans[i]<<" ";
return 0;
}