【CF605C】Freelancer's Dreams-凸包

测试地址:Freelancer’s Dreams
题目大意: n n n项工作,每项工作有两项属性 a i , b i a_i,b_i ai,bi,表示做 1 1 1单位时间可以获得 a i a_i ai的经验和 b i b_i bi的钱,现在要攒够 p p p的经验和 q q q的钱,且任意一个时刻只能做一项工作,每项工作进行的时间可以是任意非负实数,问要达到目标最少需要工作多少时间?
做法: 本题需要用到凸包。
好题。我们把一项工作看成一个向量,首先来看一个向量 ( a , b ) (a,b) (a,b)可以满足的条件的区域,这显然是 { ( p , q ) ∣ p ≤ a , q ≤ b } \{(p,q)|p\le a,q\le b\} {(p,q)pa,qb}这块区域,在二维平面中是一个边平行于坐标轴的矩形。
我们再看当总时间 ≤ 1 \le 1 1时,两个向量 ( a 1 , b 1 ) , ( a 2 , b 2 ) (a_1,b_1),(a_2,b_2) (a1,b1),(a2,b2)通过线性组合能构造出的向量的区域,我们知道 θ ⋅ ( a 1 , b 1 ) + ( 1 − θ ) ⋅ ( a 2 , b 2 ) ( 0 ≤ θ ≤ 1 ) \theta\cdot (a_1,b_1)+(1-\theta)\cdot (a_2,b_2)(0\le \theta \le 1) θ(a1,b1)+(1θ)(a2,b2)(0θ1)是两点间线段的一个方程,这就意味着这条线段是能构造出的向量区域的边界,因此两个向量加上这条向量的边界所构成的三角形就是所求的区域。
拓展到多个向量的情况,能构造出的区域就是原点加上这些向量点所构成的凸包了。能构造出的向量的区域是这个凸包,而根据上面的讨论,向量能满足的条件的区域是一个矩形,因此我们分别求出 x , y x,y x,y坐标的最大值,加上 ( 0 , m a x y ) , ( m a x x , 0 ) (0,maxy),(maxx,0) (0,maxy),(maxx,0)两个点做凸包,能得到的就是总时间 ≤ 1 \le 1 1时能满足的条件的区域了。
那么现在要扩展到总时间 ≤ t \le t t的情况,显然就是将这个凸包放大(或缩小)至原来的 t t t倍。因此要求最小的 t t t使得能满足条件 ( p , q ) (p,q) (p,q),实际上就是求一个 t t t使得凸包正好包含点 ( p , q ) (p,q) (p,q)。因为这个放大(或缩小)可以近似地看做关于原点位似,所以我们从原点向 ( p , q ) (p,q) (p,q)连边,与总时间 ≤ 1 \le 1 1的凸包交于点 P P P,答案就是原点到 ( p , q ) (p,q) (p,q)的距离与原点到 P P P的距离的比值。那么我们就以 O ( n log ⁡ n ) O(n\log n) O(nlogn)的时间复杂度解决了这一题。
以下是本人代码:

#include <bits/stdc++.h>
using namespace std;
const double eps=1e-7;
int n,st[100010],top;
double P,Q;
struct point
{
	double x,y;
}p[100010];
double operator * (point a,point b) {return a.x*b.y-b.x*a.y;}
point operator + (point a,point b) {point s={a.x+b.x,a.y+b.y};return s;}
point operator - (point a,point b) {point s={a.x-b.x,a.y-b.y};return s;}
point operator * (point a,double b) {point s={a.x*b,a.y*b};return s;}

double dis(point a,point b)
{
	return (a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y);
}

bool cmp(point a,point b)
{
	if (abs((a-p[1])*(b-p[1]))<eps)
		return dis(p[1],a)<dis(p[1],b);
	return (a-p[1])*(b-p[1])>0;
}

void graham_scan()
{
	sort(p+2,p+n+1,cmp);
	top=0;
	for(int i=1;i<=n;i++)
	{
		while(top>1&&(p[st[top]]-p[st[top-1]])*(p[i]-p[st[top]])<eps)
			top--;
		st[++top]=i;
	}
}

point inter(point p,point v,point q,point w)
{
	point u=p-q;
	double t=(w*u)/(v*w);
	return p+(v*t);
}

int main()
{
	scanf("%d%lf%lf",&n,&P,&Q);
	double mxx=0.0,mxy=0.0;
	for(int i=1;i<=n;i++)
	{
		scanf("%lf%lf",&p[i].x,&p[i].y);
		mxx=max(mxx,p[i].x);
		mxy=max(mxy,p[i].y);
	}
	p[++n].x=0.0,p[n].y=0.0;swap(p[1],p[n]);
	p[++n].x=0.0,p[n].y=mxy;
	p[++n].x=mxx,p[n].y=0.0;
	
	graham_scan();
	point Target={P,Q};
	double ans;
	for(int i=2;i<top;i++)
	{
		point joint=inter(p[1],Target,p[st[i]],p[st[i+1]]-p[st[i]]);
		double x1=p[st[i]].x,y1=p[st[i]].y,x2=p[st[i+1]].x,y2=p[st[i+1]].y;
		if (x1>x2) swap(x1,x2);
		if (y1>y2) swap(y1,y2);
		if (joint.x>=x1-eps&&joint.x<=x2+eps&&joint.y>=y1-eps&&joint.y<=y2+eps)
		{
			ans=sqrt(dis(p[1],Target)/dis(p[1],joint));
			break;
		}
	}
	printf("%.10lf",ans);
	
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值