链接:https://ac.nowcoder.com/acm/contest/3002/C
来源:牛客网
题意:
umi对弓道非常痴迷。
有一天,她在研究一个射箭问题:
在一个无限大的平面中,她站在 (x0,y0) 这个坐标。
有 n个靶子,第 i 个靶子的坐标是(xi,yi)
umi准备在 x 轴或 y 轴上放置一块挡板来挡住弓箭的轨迹,使得她可以射中的靶子数量不超过k 个。
她想知道挡板的最短长度是多少?
注:假定弓箭的轨迹是起点为umi坐标、长度无穷大的射线。umi和靶子的体积可以无视。挡板的边缘碰到弓箭轨迹也可视为挡住弓箭。
注2:挡板不能弯折,起始和终点必须在同一坐标轴上。
输入描述:
第一行为两个整数x0,y0,代表umi坐标
第二行两个整数n和k,分别代表靶子的总数量、放置挡板后可射中靶子的最大值
接下来n行,每行两个整数xi,yi,代表每个靶子的坐标
保证没有任何一个点再坐标轴上,保证没有重合两点
(1<=n<=100000,0<=k<=n-2,-2*10e9<=xi,yi<=2*10e9)
输出描述:
若无论如何无法保证可以射中的靶子数量不超过k个,则输出-1.
否则输出挡板的最小值。如果你和正确答案的误差不超过10e-6,则视为答案正确。
示例1:
输入:
1 1
2 0
-1 2
-2 1
输出:
0.50000000
题解说对于cf难度分数为2000,咱没那水平也分析不出来。不过画个图之后思路就比较清晰了。
首先知道我们只能放一个挡板,不是在x轴就是在y轴,但最终剩下的点的个数肯定为k个,所以我们的挡板需要挡住n-k个点。
然而,若x轴上的点和y轴上的点都超过k(也包含对角象限的情况,即在x,y轴上都会存在挡点),判断起来就很麻烦了,博主最开始就在这里卡住。发觉这道题着实难。
于是我们可以逆着来,我们需要这个挡板挡住n-k个点,那么只要x轴上或y轴上的点数超过n-k就可以了!就不用管对角象限的情况了。这道题就变得非常简单。
先把x,y轴上的点分别存入v2,v1,再对两个轴分别遍历。怎么遍历呢,这就用到了双指针(博主也是头回碰)。设这个挡板的起始位置为i,那么末位置就应该为i+n-k-1。头指针和尾指针同时前进,求得最小值!。
#include<iostream>
#include<algorithm>
#include<vector>
#include<cstdio>
using namespace std;
int main(){
int x0,y0,n,k;
double x,y,res;
vector<double> v1,v2;
cin>>x0>>y0>>n>>k;
k=n-k; //逆向关键之处
for(int i=0;i<n;i++){
cin>>x>>y;
if(x*x0<0) v1.push_back(y0-x0*(y0-y)/(x0-x)); //在y轴设置挡板,存入对应y值坐标
if(y*y0<0) v2.push_back(x0-y0*(x0-x)/(y0-y)); //在x轴设置挡板 存入对应x值坐标
}
sort(v1.begin(),v1.end()); //坐标排序,依次遍历
sort(v2.begin(),v2.end());
res=1e18;
if(v1.size()>=k){
int head=0,tail=k-1;
while(tail<v1.size()){
res=min(res,v1[tail]-v1[head]);
tail++;head++;
}
}
if(v2.size()>=k){
int head=0,tail=k-1;
while(tail<v2.size()){
res=min(res,v2[tail]-v2[head]);
tail++;head++;
}
}
if(res==1e18) cout<<"-1"<<endl;
else
printf("%.7lf\n",res);
}