题解 P2600 【[ZJOI2008]瞭望塔】

xzyxzy

2018-07-12 17:14:48

Solution

### Po姐写了退火 ### TA博客访问量太高,然后又有布布扣这些东西 ### 于是这道题搜出来一版的退火 ### 我竟然信了 为了不辜负我四个小时的调参,我写份题解: 如果我们确定了横坐标,那么纵坐标可以二分出来 要求所有的折点与(xx,yy)的连线要是顺时针的,所以就是xx左边的点连向他左边斜率递增,右边也递增 然后高度与关于x是一个有峰的函数(~~为什么我不觉得是单峰~~) 所以可以退火来找横坐标! ### 最最重要的Tip: 这种计算几何用退火做而且精度要求不低的,退完火之后要在答案附近Rand上个千把次才是对的(~~之前没有加只有10分~~) 类似的这种题还有[JSOI2004]平衡点 / 吊打XXX、[POJ]Run Away,都是要在退火之后的答案附近找更优解的 ```cpp #include<iostream> #include<cstdio> #include<cstdlib> #include<cmath> #define double long double using namespace std; const int N=400; int n,x[N],y[N]; double ans=1e15,lst,pos; int Check(double xx,double yy) { double lst=-1e15; int i; for(i=1;i<=n&&x[i]<xx;i++) { double a=1.0*(yy-y[i])/(xx-x[i]); if(a<lst) return 0; lst=a; } if(x[i]==xx) i++; lst=-1e15; for(;i<=n;i++) { double a=1.0*(yy-y[i])/(xx-x[i]); if(a<lst) return 0; lst=a; } return 1; } double Calc(double xx) { double l=0,r=1e11,res=1e12; while(r-l>1e-4) { double mid=(l+r)/2; if(Check(xx,mid)) r=mid,res=mid; else l=mid; } return res; } double Find(double xx) { for(int i=1;i<=n;i++) if(x[i]<xx&&x[i+1]>xx&&i!=n) return 1.0*(y[i+1]-y[i])/(x[i+1]-x[i])*(xx-x[i])+y[i]; else if(x[i]==xx) return y[i]; return 0; } double Rand(double l,double r) { double d=rand()%1000/1000.0; return d*(r-l)+l; } void SA(double T) { double y; for(;T>1e-5;T*=0.99) { double d=Rand(-1,1)*T; if(pos+d>x[n]||pos+d<x[1]) continue; y=Find(pos+d); double h=Calc(pos+d)-y; if(h<lst||exp((lst-h)/T)>Rand(0,1)) { ans=fmin(ans,h); pos=pos+d;lst=h; } } for(int i=1;i<=1000;i++) { double d=T*Rand(-1,1); if(pos+d>x[n]||pos+d<x[1]) continue; y=Find(pos+d); double h=Calc(pos+d)-y; lst=h;ans=fmin(ans,lst); } } int main() { srand(170530233); scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&x[i]); for(int i=1;i<=n;i++) scanf("%d",&y[i]); for(int t=1;t<=5;t++) { int p=rand()%n+1; pos=x[p]; lst=Calc(pos)-y[p]; ans=fmin(ans,lst); SA(x[n]-x[1]); } if(ans<0) ans=0; printf("%.3Lf\n",ans); } ``` ~~轻轻松松倒数Rk1~~ ![啊啊啊]( https://cdn.luogu.com.cn/upload/pic/23112.png )