1. 程式人生 > >[BZOJ4570][SCOI2016]妖怪(凸包)

[BZOJ4570][SCOI2016]妖怪(凸包)

兩種做法,前一種會TLE。

 

第一種是高一數學題做法,設一個妖怪的atk和dnf分別為x和y,則它在(a,b)環境下的戰鬥力為x+y/a*b+y+x/a*b。

設t為b/a,則戰鬥力即$f(x,y,t)=x+y+tx+\frac{y}{t}$,其中$t\in(0,+\infty)$。

二分答案c,問題轉化為求是否存在t滿足,對於所有妖怪,都有$f(x,y,t)<=c$。

兩邊同乘t並移項,得$xt^2+(x+y-c)t+y<=0$,可以通過解二次不等式得出t的範圍。所有妖怪的可行t的交集不為空則c可行。

複雜度$O(n\log INF)$

 

第二種將每個妖怪看成點(x,y),每個環境看成斜率為$-\frac{b}{a}$的直線,則每個妖怪在某環境下的戰鬥力就是過它的直線x,y軸截距之和,即$x+y-kx-\frac{y}{k}$。

顯然可能更新答案的點一定在上凸包上。建立上凸包,某斜率下的妖怪戰鬥力最大值就是該直線切凸包時的橫縱截距和。

根據對勾函式性質可知,$-kx-\frac{y}{k}$的最大值在$k_0=-\sqrt{\frac{y}{x}}$時取到。

對於凸包上的每個點,若這個點的$k_0$與凸包的切點就是這個點,則用它更新答案,否則,由於對勾函式在極值點兩側都是單調的,所以對每個點都只需要考慮它與左邊的點和右邊的點連成的直線即可。

複雜度瓶頸在排序,$O(n\log n)$

 1 #include<cmath>
 2 #include<cstdio>
 3 #include<algorithm>
 4
#define rep(i,l,r) for (int i=(l); i<=(r); i++) 5 using namespace std; 6 7 const int N=1000010; 8 double ans=1e30,eps=1e-10; 9 int n,top; 10 struct P{ double x,y; }p[N],s[N]; 11 bool operator <(const P &a,const P &b){ return (a.x==b.x) ? a.y>b.y : a.x<b.x; } 12 13 double
chk(P &a,P &b,P &c){ return (b.x-a.x)*(c.y-b.y)-(c.x-b.x)*(b.y-a.y); } 14 double sl(P &a,P &b){ return (b.y-a.y)/(b.x-a.x); } 15 double cal(P &a,double k){ return (fabs(k)<eps) ? 1e30 : a.x+a.y-a.x*k-a.y/k; } 16 17 int main(){ 18 freopen("bzoj4570.in","r",stdin); 19 freopen("bzoj4570.out","w",stdout); 20 scanf("%d",&n); 21 rep(i,1,n) scanf("%lf%lf",&p[i].x,&p[i].y); 22 sort(p+1,p+n+1); 23 rep(i,1,n){ 24 if (fabs(s[top].x-p[i].x)<eps) continue; 25 while (top>1 && chk(s[top-1],s[top],p[i])>0) top--; 26 s[++top]=p[i]; 27 } 28 rep(i,1,top){ 29 double k=-sqrt(s[i].y/s[i].x); 30 if ((i==1 || k<=sl(s[i-1],s[i])) && (i==top || k>=sl(s[i],s[i+1]))) ans=min(ans,cal(s[i],k)); 31 if (i>1) ans=min(ans,cal(s[i],sl(s[i-1],s[i]))); 32 } 33 printf("%.4lf\n",ans); 34 return 0; 35 }