题目:luogu3959.
题目大意:给定一张
n
n
n个点和
m
m
m条边的图,让你选择一棵生成树,并选定一个根,那么这棵生成树的价格极即为每个节点的价格之和,一个节点的介个为这个节点到根的所经过的节点数乘上它到它的父亲的边权.现在要求输出最小价格.
1
≤
n
≤
12
,
1
≤
m
≤
1
0
3
1\leq n\leq 12,1\leq m\leq 10^3
1≤n≤12,1≤m≤103.
这道题一看到就像最小生成树,然而很明显最小生成树是错的.
考虑暴力?太慢.考虑剪枝?不会.考虑状压?不会.
是时候拿出神奇的随机化算法啦!
我们考虑prim求解最小生成树的过程是一个贪心,然而这个贪心不一定能得到最优解.
我们思考贪心会错的本质,其实就是贪心会陷入一个局部最优解,而全局最优解不一定是局部最优解.
所以,贪心的最大缺点就是会陷入一个局部最优解而跳不出来!所以我们可以让这个贪心有一定的概率不选择最优解而选择其他较劣的解.
那么现在的难点就是在如何选择这个概率了.我们想到,可以让概率越来越小,那么最后答案就会越来越稳定,这样做就很可行了.
事实上这就是模拟退火算法的中心思想,不过由于这道题可以随机很多次,并不需要像模拟退火一样把概率选得十分优秀,所以我们随机化贪心一下即可.
具体来说,考虑prim的时候,选取最小值时,我们考虑在加一个可以更新最小值的条件,即一个较小的概率可以直接选择这个点.我们还需要控制这个概率让它越来越小,但是在prim的过程中可选集合会自然变小,概率也会随之减小,所以我们只需要随机一个 1 1 1到 n n n的数并判定是否在可选集合中即可.
那么代码如下:
#include<bits/stdc++.h>
using namespace std;
#define Abigail inline void
typedef long long LL;
const int N=12,INF=((1<<30)-1)/12;
int Rand(int n){return rand()%n;}
int n,m,e[N+9][N+9];
int dis[N+9],cnt[N+9],vis[N+9];
int Prim(int st){
for (int i=0;i<=n;++i) cnt[i]=1,dis[i]=INF,vis[i]=0;
dis[st]=0;
int res=0;
for (int i=1;i<=n;++i){
int t=0;
for (int j=1;j<=n;++j)
if (!vis[j]&&dis[j]<dis[t]) t=j;
if (Rand(2)){
int tmp=Rand(n)+1;
if (!vis[tmp]&&dis[tmp]^INF) t=tmp;
}
vis[t]=1;res+=dis[t];
for (int j=1;j<=n;++j)
if (!vis[j]&&e[t][j]*cnt[t]<dis[j]) dis[j]=e[t][j]*cnt[t],cnt[j]=cnt[t]+1;
}
return res;
}
int ans;
Abigail into(){
scanf("%d%d",&n,&m);
for (int i=1;i<=n;++i)
for (int j=1;j<=n;++j) e[i][j]=INF;
for (int i=1;i<=m;++i){
int x,y,v;
scanf("%d%d%d",&x,&y,&v);
e[x][y]=e[y][x]=min(e[x][y],v);
}
}
Abigail work(){
ans=Prim(1);
for (int i=1;i<=n;++i)
for (int j=1;j<=100;++j) ans=min(ans,Prim(i));
}
Abigail outo(){
printf("%d\n",ans);
}
int main(){
into();
work();
outo();
return 0;
}