我们首先,如果两个点之间有连多条边,肯定只有最短的那条最优。
那么我们进行状压,对于某一个状态\(S_{0}\),我们维护它的所有拓展的集合\(T_{S_{1}}\)
然后,记\(f_{i,k}\)表示,当前状态为\(i\),当前的深度为\(k\)时的最小花费。
这样拓展,每一次拓展都相当于把深度加深了一层,因此就可以不要花费太多的精力去考虑\(k\)对贡献的影响。
而,从某一个根开始拓展,即相当于\(f_{(1<<i),0}\)
于是我们便可以开始DP。每一次拓展都会将可行集合纳入范围。这样拓展的花费也是可以被轻松计算出来的。
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define Min(_A,_B) ((_A)<(_B)?(_A):(_B))
int n,m,f[1<<12][12],usf[1<<12];
int mp[15][15];
void init(){
scanf("%d%d",&n,&m);
int u,v,w;
memset(mp,0x3f,sizeof(mp));
for(int i=1;i<=m;++i){
scanf("%d%d%d",&u,&v,&w);
--u,--v;
mp[u][v]=Min(mp[u][v],w);
mp[v][u]=Min(mp[v][u],w);
}
const int MAX=1<<n;
for(int i=1;i<MAX;++i){
for(int j=0;j<n;++j){
if((i|(1<<j))!=i){
continue;
}
for(int k=0;k<n;++k){
if(mp[j][k]!=0x3f3f3f3f){
usf[i]|=(1<<k);
}
}
}
}
memset(f,0x3f,sizeof(f));
for(int i=0;i<n;++i){
mp[i][i]=0;
f[1<<i][0]=0;
}
long long sm,nw,x;
for(int i=2;i<MAX;++i){
for(int j=i-1;j;j=(j-1)&i){
if(((usf[i]|j)!=usf[i])){
continue;
}
sm=0,nw=i^j;
for(int k=0;k<n;++k){
if((nw>>k)&1){
x=0x3f3f3f3f;
for(int l=0;l<n;++l){
if((j>>l)&1){
x=Min(x,mp[l][k]);
}
}
sm+=x;
}
}
for(int k=1;k<n;++k){
f[i][k]=Min(f[i][k],f[j][k-1]+sm*k);
}
}
}
int ans=0x3f3f3f3f;
for(int i=0;i<n;++i){
ans=Min(ans,f[MAX-1][i]);
}
printf("%d\n",ans);
}
int main(){
init();
return 0;
}