1. 程式人生 > >【NOIP2017提高】寶藏——(假)模擬退火入門

【NOIP2017提高】寶藏——(假)模擬退火入門

題目:luogu3959.

題目大意:給定一張圖,讓你選擇一棵生成樹,並選定一個根,那麼這棵生成樹的價格極即為每個節點的價格之和,一個節點的介個為這個節點到根的所經過的節點數乘上它到它的父親的邊權.現在要求輸出最小价格.

這道題一看到就像最小生成樹,然而很明顯最小生成樹是錯的.

考慮暴力?太慢.考慮剪枝?不會.考慮狀壓?不會.

是時候拿出神奇的隨機化演算法啦!

我們考慮prim求解最小生成樹的過程是一個貪心,然而這個貪心不一定能得到最優解.

我們思考貪心會錯的本質,其實就是貪心會陷入一個區域性最優解,而全域性最優解不一定是區域性最優解.

所以,貪心的最大缺點就是會陷入一個區域性最優解而跳不出來!所以我們可以讓這個貪心有一定的概率不選擇最優解而選擇其他較劣的解.

那麼現在的難點就是在如何選擇這個概率了.我們想到,可以讓概率越來越小,那麼最後答案就會越來越穩定,這樣做就很可行了.

實際上真實的模擬退火演算法應該比這個要複雜很多,但其實感覺上這個演算法好像也夠了,而且其實這個演算法根本就是個隨機化貪心.

現在考慮將這個模擬退火演算法應用到這個題上,考慮prim的時候,選取最小值時,我們考慮在加一個可以更新最小值的條件,即一個較小的概率可以直接選擇這個點.我們還需要控制這個概率讓它越來越小,但是在prim的過程中可選集合會自然變小,概率也會隨之減小,所以我們只需要隨機一個1~n的數並判定是否在可選集合中即可.

但是這樣做還是有較大概率不是最優解,所以我們可以進行多次這樣的演算法,由於資料小,我們進行1000次也沒有關係.

那麼程式碼如下:

#include<bits/stdc++.h>
  using namespace std;
#define Abigail inline void
typedef long long LL;
typedef unsigned int UI;
const int N=12;
const LL INF=(1LL<<40LL)-1LL;
int n,m,use[N+9];
LL ans=INF,e[N+9][N+9],cnt[N+9],sum,dis[N+9];
bool check(){return rand()%2;} 
int random(int x){UI y=rand()*rand();return y%x+1;}
LL prim(int st){
  for (int i=0;i<=n;++i)
    cnt[i]=1,use[i]=0,dis[i]=INF;
  cnt[st]=1;dis[st]=0;
  sum=0;
  for (int i=1;i<=n;++i){
    int v=0,vv;
    for (int j=1;j<=n;++j)
      if (!use[j]&&dis[j]<dis[v]) v=j;
    if (check()){
      vv=random(n);
      if (!use[vv]) v=vv;
    }
    use[v]=1;sum+=dis[v];
    for (int j=1;j<=n;++j)
      if (!use[j]&&cnt[v]*e[v][j]<dis[j]) dis[j]=(cnt[v])*e[v][j],cnt[j]=cnt[v]+1;
  }
  return sum;
}
Abigail into(){
  scanf("%d%d",&n,&m);
  int x,y;
  LL v;
  for (int i=1;i<=n;++i)
    for (int j=1;j<=n;++j)
     if (i^j) e[i][j]=INF;
  for (int i=1;i<=m;++i){
    scanf("%d%d%lld",&x,&y,&v);
    if (e[x][y]<=v) continue;
    e[x][y]=e[y][x]=v;
  }
}
Abigail work(){
  for (int i=1;i<=1000;++i)
    for (int j=1;j<=n;++j)
      ans=min(ans,prim(j));
}
Abigail outo(){
  printf("%d\n",ans);
}
int main(){
  srand(time(0));
  into();
  work();
  outo();
  return 0;
}