BZOJ3680:吊打XXX
阿新 • • 發佈:2018-09-05
傳送門 cal esp 最終 模擬 計算 理解 ret pro 個點的中心位置,這樣可以減少轉移次數。再者,一個繩結如果不在最終目標點的話,那麽它肯定有往最終目標點移動的趨勢。所以我們在溫度很高的時候,繩結坐標的跳動距離就設置大一點,就先向趨勢所在的方向跳。慢慢的溫度低了,我們就跳小一點的步子,慢慢穩定下來,這樣子最後甚至可以準確的命中正確答案!
我對模擬退火的理解:https://www.cnblogs.com/AKMer/p/9580982.html
題目傳送門:https://www.lydsy.com/JudgeOnline/problem.php?id=3680
模擬退火在計算幾何方面有很大的用處,特別是求費馬點一類的問題。
題目意思就是要你找平面上的廣義費馬點。
所謂費馬點,就是在一個\(n\)邊形內,這個點如果到\(n\)個頂點距離和最小,那麽它就是這個\(n\)邊形的費馬點。
然後廣義費馬點就是頂點上帶權的費馬點,統計的時候距離要乘上這個權值再加起來。
我們靈性的腦補一波就可以發現,這個題目顯然不存在局部最優解,所以我們可以大膽爬山。
然後我們再靈性的想一想:首先我們可以把初始點定在\(n\)
然後因為這個性質,爬山算法就比模擬退火在這道題上優秀得多了。爬山算法可以直接在\(BZOJ\)上\(AC\),但是模擬退火不行。
畢竟沒有局部最優解你還接受更加差的狀態就顯得很智障了……
時間復雜度:\(O(能A)\)
空間復雜度:\(O(能A)\)
爬山算法\(AC\)代碼如下:
#include <cmath> #include <ctime> #include <cstdio> #include <algorithm> using namespace std; #define sqr(a) ((a)*(a)) const int maxn=1e4+5; int n; double ansx,ansy,ans=1e18;//ans記錄最小權值,ansx記錄歷史最優節點的x,ansy記錄歷史最優節點的y struct gty { double x,y,w;//x,y存坐標,w存重量 }point[maxn]; double len() { double x=rand()%200000-100000; return x/100000; }//隨機一個-100000~100000之間的長度 double dis(double x1,double y1,double x2,double y2) { return sqrt(sqr(x1-x2)+sqr(y1-y2)); }//求(x1,y1),(x2,y2)兩點之間的距離 double calc(double x,double y) {//計算以點(x,y)為繩結時候的權值 double tmp=0; for(int i=1;i<=n;i++) tmp+=dis(x,y,point[i].x,point[i].y)*point[i].w;//直接暴力算 if(tmp<ans) {ans=tmp;ansx=x,ansy=y;}//如果權值比歷史最優更小,那麽就更新歷史最優 return tmp;//返回權值 } int main() { srand(time(0)); scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%lf%lf%lf",&point[i].x,&point[i].y,&point[i].w); ansx+=point[i].x;ansy+=point[i].y; }ansx/=n;ansy/=n;double now_x=ansx,now_y=ansy;//讀入以及初始化 for(double T=10000;T>=1e-5;T*=0.98) { double nxt_x=now_x+len()*T,nxt_y=now_y+len()*T;//爬山,每次跳len*T那麽長,那麽隨著溫度慢慢降低也會趨向穩定 if(calc(now_x,now_y)>calc(nxt_x,nxt_y)) now_x=nxt_x,now_y=nxt_y;//如果這個方向是繩結移動的趨勢方向那麽就直接跳過去 } printf("%.3lf %.3lf\n",ansx,ansy);//輸出 return 0; }
模擬退火不能\(AC\)代碼如下:
#include <cmath> #include <ctime> #include <cstdio> #include <algorithm> using namespace std; #define sqr(a) ((a)*(a)) const int maxn=1e4+5; const double T_0=1e-5; const double del_T=0.98; int n; double ansx,ansy,ans=1e18; struct gty { double x,y,w; }point[maxn]; double dis(double x1,double y1,double x2,double y2) { return sqrt(sqr(x1-x2)+sqr(y1-y2)); } double calc(double x,double y) { double tmp=0; for(int i=1;i<=n;i++) tmp+=dis(x,y,point[i].x,point[i].y)*point[i].w; if(tmp<ans) {ans=tmp;ansx=x,ansy=y;} return tmp; } double len() { double x=rand()%200000-100000; return x/100000; } void Anneal() { double T=1e4,now_x=ansx,now_y=ansy; while(T>=T_0) { double nxt_x=now_x+len()*T; double nxt_y=now_y+len()*T; double tmp1=calc(now_x,now_y); double tmp2=calc(nxt_x,nxt_y); if(tmp2<tmp1||exp((tmp1-tmp2)/T)*RAND_MAX>rand())//除了這裏和爬山算法是一模一樣的。這裏加了一個模擬退火特有的“接受不優於當前狀態的狀態”的概率 now_x=nxt_x,now_y=nxt_y; T*=del_T; } } int main() { srand(time(0)); scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%lf%lf%lf",&point[i].x,&point[i].y,&point[i].w); ansx+=point[i].x,ansy+=point[i].y; }ansx/=n,ansy/=n; for(int i=1;i<=54;i++)Anneal(); printf("%.3lf %.3lf\n",ansx,ansy); return 0; }
BZOJ3680:吊打XXX