bzoj:2428: [HAOI2006]均分數據 模擬退火

題目鏈接

bzoj2428

題目描述

Description
已知N個正整數:A1、A2、……、An 。今要將它們分成M組,使得各組數據的數值和最平均,即各組的均方差最小。均方差公式如下:這裏寫圖片描述
,其中σ爲均方差,這裏寫圖片描述是各組數據和的平均值,xi爲第i組數據的數值和。

Input

第一行是兩個整數,表示N,M的值(N是整數個數,M是要分成的組數)
第二行有N個整數,表示A1、A2、……、An。整數的範圍是1–50。
(同一行的整數間用空格分開)

Output

這一行只包含一個數,表示最小均方差的值(保留小數點後兩位數字)。

Sample Input

6 3
1 2 3 4 5 6

Sample Output

0.00

HINT

對於全部的數據,保證有K<=N <= 20,2<=K<=6

題解

模擬退火的過程也和真實的退火類似:首先設定初溫,然後慢慢降溫,降溫的同時讓解對應的點移動。每次從當前點試圖移動到另一個點時,若另一個點的函數值優於當前點的函數值,則接受這個移動,否則以一定的概率接受移動。這個概率隨着溫度的降低而減小。已有人證明,只要運行的時間足夠長,模擬退火是一定可以獲得最優解的。

1 設定初溫T0 以及初始解S。
2 在S 的鄰域內隨機選取一個解S ′,若E(S ′) < E(S),則令
S=S ′,否則令Δt = E(S ′)-E(S),以T/T0的概率令S=S ′。
3 減小T 的值,若T< T1 則停止,否則轉(2)。

我們可以在相同的溫度下執行若干次步驟(2) 後再進行降溫。模擬退火中有幾個重要的參數:初始溫度T0,相同溫度下的迭代次數k,以及降溫的方式(線性、指數)。如果參數設置得當,可能在很快就可以得到最優解或者非常優的次優解。反之可能運行幾個小時都只能得到很差的解。考試時,我一般將T0 的值設在1000 ~10000 左右,降溫的方式一般採取指數下降,即每次令T=a*T,a是一個小於1 的值,比如0.9、0.99999 等。而迭代次數根據考試時間(提交答案題)或者題目時限(傳統題)而定。有時降溫方式採取線性下降會取得更好的效果。
總而言之,考試時不要隨意地選擇參數,應該多試幾組參數,通過向屏幕輸出當前最優解來判斷參數的好壞。

在此題中首先隨機設定每個元素所在的組,然後求出初始的方差,然後開始退火,每次退火時首先隨機選一個元素t,並得到它所在的組x,然後需要選一組y,把t放入組y中,若溫度高的話,此時狀態不穩定,很難使最終的搜索結果儘量更優,那麼就需要貪心地選當前元素值之和最小的組。若溫度低的話,此時狀態比較穩定,而且各個組的元素和基本上差不多,此時就需要隨機選個y,然後嘗試將t放入組y中,若新解更優,則把舊解更新爲新解,否則在[0,T0)隨機一個數,若隨機的數比T小,那麼就把舊解更新爲新解(T越小,在這種情況下更新解的概率也就越小,T的大小相當於是解的可靠性,T越小,說明此時解越可靠,在新解並不優的情況下改變原有解的概率也就越小),否則保持原有的解不變。

我們需要重複上述過程多次,就很可能得到最優解了。


#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
using namespace std;

int n,m,pos[30],sum[30],a[30];
double ans,minans=1e9,T,avr;

void solve(){
    memset(sum,0,sizeof(sum));
    ans=0; T=10000;
    for(int i=1;i<=n;i++){
        pos[i]=rand()%m+1;
        sum[pos[i]]+=a[i];
    }
    for(int i=1;i<=m;i++) ans+=(sum[i]-avr)*(sum[i]-avr);
    while(T>0.1){
        T*=0.9;
        int t=rand()%n+1,x=pos[t],y;
        if(T>500) y=min_element(sum+1,sum+1+m)-sum;
        else y=rand()%m+1;
        if(x==y) continue;
        double tmp=ans;
        ans-=(sum[x]-avr)*(sum[x]-avr);
        ans-=(sum[y]-avr)*(sum[y]-avr);
        sum[x]-=a[t]; sum[y]+=a[t];
        ans+=(sum[x]-avr)*(sum[x]-avr);
        ans+=(sum[y]-avr)*(sum[y]-avr);
        if(ans<=tmp||rand()%10000<=T) pos[t]=y;
        else{
            sum[x]+=a[t]; sum[y]-=a[t]; ans=tmp;
        }
        minans=min(minans,ans);
    }
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]),avr+=a[i];
    avr=avr/m;
    for(int i=1;i<=10000;i++) solve();
    printf("%.2f\n",sqrt(minans/m));
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章