1. 程式人生 > >[2018.3.26集訓]yja-偽模擬退火

[2018.3.26集訓]yja-偽模擬退火

a.out n) 拉格朗日 name 拉格朗日乘數法 double 否則 pen goto

題目大意

在平面上找 n 個點, 要求這 n 個點離原點的距離分別為 $r_1,r_2, \cdots r_rn$. 最大化這 n 個點構成的凸包面積, 凸包上的點的順序任意。
註意點不一定全都要在凸包上。

$n \leq 8,r_i \leq 1000$

題解

正解是拉格朗日乘數法。
然而作為一只蒟蒻,當然是不會這種巧妙的操作的。

那就亂搞吧!
考慮使用模擬退火算法。

初始給每個節點隨機一個相對於原點的角度,並計算它們構成的凸包面積。
設定初始溫度為$\pi$(角度為弧度制),並進行退火。
每次從所有點中隨機選擇一個點,對其角度加上或減去溫度乘上一個在$(0,1)$範圍內隨機的實數,計算此時的答案。

若更優,則應用這次修改,否則不作出修改。

隨機$10$次左右即可穩定得到最優解~
說這是偽模擬退火的原因是,模擬退火事實上還會根據溫度一定概率接受一次錯誤的修改,而實測這樣的效果並不是特別優。

代碼:

#include<bits/stdc++.h>
using namespace std;

typedef double db;
typedef pair<db,db> pr;
const int N=19;
const db eps=1e-8;
const db pi=acos(-1);
const db mint=1e-7;
#define x first
#define y second
int n,stk[N],rr[N]; db r[N]; db ang[N],ans=0.0; pr p[N]; inline bool cmp(pr a,pr b) { if(abs(a.x-b.x)<eps) return a.y<b.y; return a.x<b.x; } pr operator - (pr a,pr b) { return pr(a.x-b.x,a.y-b.y); } inline bool cmp2(pr a,pr b) { return atan2(a.y-p[1].y,a.x-p[1].x)<atan2(b.y-p[1
].y,b.x-p[1].x); } inline db randf() { return (db)rand()/(db)RAND_MAX; } inline db cross(pr a,pr b) { return a.x*b.y-a.y*b.x; } inline db hull() { for(int i=1;i<=n;i++) p[i]=pr(cos(ang[i])*r[i],sin(ang[i])*r[i]); sort(p+1,p+n+1,cmp); sort(p+2,p+n+1,cmp2); int top; stk[top=1]=1; for(int i=2;i<=n;i++) { while(top>=2 && cross(p[i]-p[stk[top-1]],p[stk[top]]-p[stk[top-1]])>eps) top--; stk[++top]=i; } db ret=0; for(int i=1;i<top;i++) ret+=cross(p[stk[i]],p[stk[i+1]]); ret+=cross(p[stk[top]],p[stk[1]]); return fabs(ret/2.0); } inline void work() { for(int i=1;i<=n;i++) ang[i]=randf()*pi*2.0; db t=pi,cans=hull(); while(t>mint) { //for(int i=1;i<=1;i++) { int choose=rand()%n+1; db inc=t*(randf()-0.5); ang[choose]+=inc; db tans=hull(); if(tans>cans)cans=tans; else ang[choose]-=inc; } t*=0.994; } ans=max(ans,cans); } inline int spj() { for(int i=1;i<=n;i++) ang[i]=((db)(i-1)/(db)n)*pi*2.0; printf("%.6f\n",hull()); return 0; } int main() { freopen("yja.in","r",stdin); freopen("yja.out","w",stdout); srand(time(0)); scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%d",&rr[i]); r[i]=(db)rr[i]; } for(int i=2;i<=n;i++) if(r[i]!=r[i-1]) goto hell; return spj(); hell:; int T=200; while(T--) work(); printf("%.8f\n",ans); return 0; }

[2018.3.26集訓]yja-偽模擬退火