首先处理出w[s1][s2]表示把s2的点都连到s1上的最小花费(s2中的每个点只能连到s1中的某个点上)。怎么处理呢?先
O(2n∗n)
做出单点到一个集合的花费,然后
O(3n∗n)
的处理出其他的集合到集合的花费。然后就可以dp了。
dp[i][S][s1]表示深度最大为i,已连接了S的点,最深层的点为s1的最小花费,我们枚举一个S的补集的子集s2来转移即可。
dp[i][S][s1]+w[s1][s2]−>dp[i+1][S|s2][s2]
复杂度
O(n∗4n)
,还要注意空间,要滚动数组。
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define ll long long
#define inf 0x3f3f3f3f
#define N 4100
inline char gc(){
static char buf[1<<16],*S,*T;
if(S==T){T=(S=buf)+fread(buf,1,1<<16,stdin);if(T==S) return EOF;}
return *S++;
}
inline int read(){
int x=0,f=1;char ch=gc();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=gc();}
while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=gc();
return x*f;
}
int n,m,dp[2][N][N],bin[15],mp[15][15],ans=inf,w[N][N];//dp[i][S][s1],深度最大为i,已连接了S的点,最深层的点为s1,
int main(){
// freopen("a.in","r",stdin);
n=read();m=read();bin[0]=1;memset(mp,inf,sizeof(mp));
for(int i=1;i<=m;++i){
int x=read()-1,y=read()-1;mp[x][y]=mp[y][x]=min(mp[y][x],read());
}for(int i=1;i<=n;++i) bin[i]=bin[i-1]<<1;memset(w,inf,sizeof(w));
for(int s1=1;s1<bin[n]-1;++s1){
for(int i=0;i<n;++i){
if(s1&bin[i]) continue;
for(int j=0;j<n;++j){
if((s1&bin[j])==0||mp[i][j]==inf) continue;
w[s1][bin[i]]=min(w[s1][bin[i]],mp[i][j]);
}
}
}for(int s1=1;s1<bin[n]-1;++s1){
int T=bin[n]-1^s1;
for(int s2=T;s2;s2=(s2-1)&T){
int tot=0;
for(int i=0;i<n;++i) if(s2&bin[i]) ++tot;
if(tot==1) continue;w[s1][s2]=0;
for(int i=0;i<n;++i){
if((s2&bin[i])==0) continue;
if(w[s1][bin[i]]==inf){w[s1][s2]=inf;break;}
w[s1][s2]+=w[s1][bin[i]];
}
}
}memset(dp,inf,sizeof(dp));
for(int i=0;i<n;++i) dp[0][bin[i]][bin[i]]=0;int now=0;
if(n==1) ans=0;
for(int i=1;i<=n;++i){
memset(dp[now^1],inf,sizeof(dp[0]));
for(int S=1;S<bin[n]-1;++S){
int T=bin[n]-1^S;
for(int s1=S;s1;s1=(s1-1)&S){
if(dp[now][S][s1]==inf) continue;
for(int s2=T;s2;s2=(s2-1)&T){
if(w[s1][s2]==inf) continue;
dp[now^1][S|s2][s2]=min(dp[now^1][S|s2][s2],dp[now][S][s1]+i*w[s1][s2]);
}
}
}now^=1;
for(int i=1;i<=bin[n]-1;++i) ans=min(ans,dp[now][bin[n]-1][i]);
}printf("%d\n",ans);
return 0;
}