看数据范围想到状压,我们知道最后是选出一颗生成树,但边权的计算有一些有趣;
我们先选一个点做根;然后就发现边的权和深度有关;那我们按深度dp;即按层dp;
dp[i][s]表示前i层选的点集为s,转移时我们枚举s的补集的子集ss;对于ss中的每个点,
我们连上他到s中点的最小边;但这样连的边没办法保证深度为i+1,但我们就强制把他设成i+1;
这样感觉很错,但其实是对的,因为这样只会使答案变大,而且还可以保证真正的答案被枚举到;
相当于是一种对限制的扩大。
#include<iostream> #include<cstring> #include<algorithm> #include<cmath> #include<cstdio> using namespace std; typedef long long ll; const ll inf=1e16; ll map[20][20],dis[15][(1<<12)+10]; ll dp[15][(1<<12)+10],z; int n,m,x,y; int main(){ cin>>n>>m; if(!m){puts("0");return 0;} for(int i=0;i<=12;++i) for(int j=0;j<=12;++j)map[i][j]=inf; for(int i=0;i<=12;++i) for(int j=0;j<=(1<<12);++j)dis[i][j]=inf; for(int i=0;i<=12;++i) for(int j=0;j<=(1<<12);++j)dp[i][j]=inf; for(int i=1;i<=m;++i){ scanf("%d%d%d",&x,&y,&z); map[x][y]=map[y][x]=min(map[x][y],z); } int sta=(1<<n)-1; for(int s=1;s<=sta;++s){ for(int i=1;i<=n;++i){ if((1<<(i-1))&s)continue; for(int j=1;j<=n;++j){ if((1<<(j-1))&s)dis[i][s]=min(dis[i][s],map[i][j]); } } } for(int i=1;i<=n;++i)dp[1][1<<(i-1)]=0; for(int s=1;s<=sta;++s){ int st=sta-s; for(int ss=st;ss;ss=(ss-1)&st){ ll res=0; for(int i=1;i<=n;++i){ if((1<<(i-1))&ss)res+=dis[i][s]; } for(ll i=1;i<n;++i){ dp[i+1][ss|s]=min(dp[i+1][ss|s],dp[i][s]+res*i); } } } ll ans=inf; for(int i=2;i<=n;++i)ans=min(ans,dp[i][sta]); cout<<ans; return 0; }