模擬退火


  對於我這個非酋 我真的比較服氣我的臉 黑的像什么一樣。學這個只是為了 考試遇到看似不可寫的題目寫一個隨機化搜索。

畢竟 隨機化到一定程度就可以完全的達到正確答案。

關於模擬退火 我只能說 真的是玄學 做題全靠隨機。 隨機 答案 隨機AC 隨機 都是隨機 。

但是當隨機到了一定次數 那么此時答案就有可能是最優解或者無限逼近最優解了哈。

爬山算法加退火,爬山基於greedy的思想 也是針對於單峰函數的做法,但是這有隨機的靠近貌似沒有什么大用。

我們可以直接三分找最值 准確高效。但是其還是非常有效的。

正解貌似是正交分解 我不是鬼才 我才不會寫正交分解因為不會寫。。。

考慮直接搜索答案 采用 模擬退火搜索 ,每次隨機出一個值如果比當前優秀 就接受這個答案並以這個答案繼續向后面搜索。

或者以一定的概率接受它 通常我們的寫法是 exp((w-c)/T)>(db)rand()/RAND_MAX 如果是這樣的話我們就接受它。

此時rand()的值就在0~RAND_MAX之間 RAND_MAX==32767沒記錯這個應該是short的最大范圍吧。

而exp(x) 是返回以e為底的x次方 到這里就非常的玄學了 玄學的接受新解。

然后 生成解 的時候也是靠隨機a=w1+T*(rand()*2-RAND_MAX); b=w2+T*(rand()*2-RAND_MAX);

生成在坐標范圍內的 新坐標。 溫度慢慢降低時 坐標的跳度也會降低這就是退火。能量降低。

//#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<ctime>
#include<cmath>
#include<queue>
#include<stack>
#include<algorithm>
#include<cctype>
#include<cstdlib>
#include<utility>
#include<iomanip>
#include<vector>
#include<deque>
#include<set>
#include<map>
#include<bitset>
#define INF 2147483646
#define min(x,y) (x>y?y:x)
#define max(x,y) (x>y?x:y)
#define up(p,i,n) for(int i=p;i<=n;++i)
#define ll long long
#define db double
#define ldb long double
#define x(i) t[i].x
#define y(i) t[i].y
#define w(i) t[i].w
using namespace std;
char buf[1<<15],*fs,*ft;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline int read()
{
    int x=0,f=1;char ch=getc();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();}
    return x*f;
}
inline void put(int x)
{
    x<0?putchar('-'),x=-x:0;
    int num=0;char ch[50];
    while(x)ch[++num]=x%10,x/=10;
    num==0?putchar('0'):0;
    while(num)putchar(ch[num--]);
    putchar('\n');return;
}
const int MAXN=1002;
const double EPS=1e-14;
int n;
int ti=5;
double ans,ansx,ansy;
struct wy
{
    int x,y;
    int w;
}t[MAXN];
inline double carculate(double x,double y)
{
    double tmp=0;
    up(1,i,n)tmp+=sqrt((x(i)-x)*(x(i)-x)+(y(i)-y)*(y(i)-y))*w(i);
    return tmp;
}    
int main()
{
    //freopen("1.in","r",stdin);
    n=read();
    up(1,i,n)
    {
        x(i)=read();
        y(i)=read();
        w(i)=read();
        ansx+=x(i);
        ansy+=y(i);
    }
    ansx/=n;ansy/=n;
    ans=carculate(ansx,ansy);
    srand(time(NULL));
    while(ti--)
    {
        double w,w1,w2;
        w=ans;w1=ansx;w2=ansy;
        for(double T=10002;T>EPS;T*=0.98)
        {
            double a,b,c;
            a=w1+T*(rand()*2-RAND_MAX);
            b=w2+T*(rand()*2-RAND_MAX);
            c=carculate(a,b);
            if(ans>c)ans=c,ansx=a,ansy=b;
            if(w>c||exp((w-c)/T)>(db)rand()/RAND_MAX)w=c,w1=a,w2=b;
        }
    }
    printf("%.3lf %.3lf",ansx,ansy);
    return 0;
}
View Code

調節好參數一般就能AC A不了也是80 看你是不是歐皇咯。

這道題就比較鬼畜一點了我調參調了1個多小時,關鍵是沒有把握到隨機的竅門 或者說我是太隨機了。

初看題目好像40分能寫爆搜 6^n 不會爆。 考慮隨機化搜索,那么此時 我們隨機化什么呢 標准差即均方差 貌似沒用驗證不了答案。

那考慮隨機化數列 可是我根本沒想出來可以隨機化數列導致看了討論才知道有 random_shuffle 這個玩意。

所以依靠它來進行 隨機化數列即可 然后 取答案的時候顯然靠dp 當然貪心解這道題會更輕松 然后我更沒想到貪心。

然后發現是50分 因為random_shuffle 這玩意實在是太過於隨機 很不容易隨機到正解況且我的dp是完全按照隨機到的東西來解當前序列來的到最優解的 所以這個不具有隨機性 此時貪心針對每一個數字放在較小的那一組里根據隨機性這個貪心貪的好 但是我要要隨機到真正的答案 我要歐皇附體那就只能 以隨機應隨機了 此時我是太過於依賴這個 random_shuffle 了 其實已經脫離了模擬退火 開始胡蒙答案了 考慮 想模擬退火一樣承接比較有優秀的狀態 那就是 直接swap 直接swap 不就好了還能承接優秀的當前數列的情況 啊哈終於卡到了80 然后我也沒有辦法了 考修改隨機種子 19260817 這玩意真好用直接AC 真猛 然后交bzoj 貌似不太行 bzoj 不認臉 那算了 我要讓隨機突破天際 於是隨機多次 然后AC

//#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<ctime>
#include<cmath>
#include<queue>
#include<stack>
#include<algorithm>
#include<cctype>
#include<cstdlib>
#include<utility>
#include<iomanip>
#include<vector>
#include<deque>
#include<set>
#include<map>
#include<bitset>
#define INF 214748364
#define min(x,y) (x>y?y:x)
#define max(x,y) (x>y?x:y)
#define up(p,i,n) for(int i=p;i<=n;++i)
#define ll long long
#define db double
#define ldb long double
using namespace std;
char buf[1<<15],*fs,*ft;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline int read()
{
    int x=0,f=1;char ch=getc();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();}
    return x*f;
}
inline void put(int x)
{
    x<0?putchar('-'),x=-x:0;
    int num=0;char ch[50];
    while(x)ch[++num]=x%10,x/=10;
    num==0?putchar('0'):0;
    while(num)putchar(ch[num--]);
    putchar('\n');return;
}
const int MAXN=22;
const db ESP=1e-4;
int n,k,flag;
db a[MAXN],s[MAXN],b[MAXN];
db ans=INF,tmp,arrange;
db f[MAXN][MAXN];//f[i][j] 表示前i個數字分成j組此時的方差值
inline void carculate()
{
    up(1,i,n)s[i]=s[i-1]+a[i];
    f[0][0]=0;
    up(1,i,n)up(1,j,k)
    {
        f[i][j]=INF;
        for(int w=j-1;w<=i;++w)
        {
            if((f[w][j-1]+(s[i]-s[w]-arrange)*(s[i]-s[w]-arrange))<f[i][j])
                f[i][j]=f[w][j-1]+(s[i]-s[w]-arrange)*(s[i]-s[w]-arrange);
        }
    }
    tmp=f[n][k]/k;
    return;
}
inline void simulatedannealing()
{
    double w=ans;
    for(db T=50000;T>ESP;T*=0.98)
    {
        int x=rand()%n+1,y=rand()%n+1;
        if(x==y)continue;
        swap(a[x],a[y]);flag=1;
        carculate();
        if(ans>tmp)ans=tmp,flag=0;
        if(w>tmp||exp(w-tmp)/T>(db)rand()/RAND_MAX)w=tmp,flag=0;
        if(flag)swap(a[x],a[y]);
    }
    up(1,i,n)
    {
        int x=rand()%n+1,y=rand()%n+1;
        if(x==y)continue;
        swap(a[x],a[y]);
    }
    return;
}
int main()
{
    //freopen("1.in","r",stdin);
    srand(time(NULL));
    n=read();k=read();
    up(1,i,n)a[i]=read(),arrange+=a[i];
    arrange/=k;
    up(0,i,n)up(0,j,k)f[i][j]=INF;
    carculate();
    if(ans>tmp)ans=tmp;
    simulatedannealing();//simulated 模擬 annealing 退火
    simulatedannealing();
    simulatedannealing();
    simulatedannealing();
    simulatedannealing();
    simulatedannealing();
    //random_shuffle(a+1,a+1+n);
    //up(1,i,n)cout<<a[i]<<' ';
    //cout<<ans<<endl;
    //cout<<sqrt(ans)<<endl;
    printf("%.2lf",sqrt(ans));
    return 0;
}
View Code

參數調的時候需要自己感受 因為參數調的越准確 越容易AC 當然這種玄學做法不知道提倡。

以下轉自 flash hu 的博客:

眾所周知,模擬退火最麻煩的地方在調參,只有合適的參數才能在一定的時間內很大概率跑出最優解。

蒟蒻的經驗暫時還不足呢qwq,不過也隨便談談吧。

首先,根據數據范圍和精度要求,可以基本確定EPS的大小了,不過也需要嘗試手動微調。

比較麻煩的是溫度和變動率。首先不必顧慮,都開大一點,先把最優解跑出來。

然后,手動二分吧,注意每個二分的值要多跑幾遍,因為模擬退火有偶然性,一次跑出最優解不代表大部分時候都能。

不過因為有兩個量,還不能直接二分,應該二分套二分比較合適

update:考試的時候寫提答,總結了一種方法:觀察法

一邊退火一邊輸出當前的溫度、解等信息,通過觀察大致感受一下解的降低速率

一般來說,如果解的降低速率比較均勻,跑出來的最優解也就好一些

不均勻的話,就調整參數,將解的降低速率較快的時間段的ΔTΔT變大一點,速率就能減慢一點。反之同理。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM