题目大意:给定一n个节点m条边的图,求权值最小的生成树。
权值的算法:以一个点为起点,每加一个点权值增加 起点到这个点的点数*所连边的权值。
n<=12,m<=1000;
题解:先考虑dfs,对于每个点dfs所有组合,复杂度很明显为O(n(n!)),对于12很明显过不去,但是这个题有70分n<=8,dfs能很轻松水到70分,所以很推荐写;
这个题目给的n的范围,就很状压,所以考虑状压dp;
定义:dpi,j表示在树高为i,状态为j时的最小价值;
j为当前讨论的集合 ,Cj为j关于全集的补集,设j’为Cj的子集,那么dpi,j就可以向dpi,j|j’转移,即:dpi,j|j’=min(dpi,j|j’,dpi,j+val j’);
只需要枚举所有的集合,并枚举其补集的子集,就可以枚举所有的状态,可以知道是能够达到最优解的;
已经知道了转移方式,那么问题就是求val j’,val j’=sigma(min dis(x,y) *(i+1))x,y分别属于j,j’,且所有x,y构成j,j’;
证明如下:
这个val j’的计算方法时认为所有j’中的点都连在了j中深度为i的节点上(即最深的一些点),那么如果有更优的解,即有点不是连在最深的节点上,那必然在循环中讨论到了,即在i小于当前i时就已经将更有的部分点转移到更小的i上并记录了,最终计算答案时会统计所有的解,也会将这种更优的讨论及由其转移来的状态统计到,不会漏解且一定为最优解。
考虑实现的方法:
先遍历所有的i值,然后遍历所有的状态数,枚举子集,计算所有的val值,然后进行dp转移;细节都在代码里,也有详细注释,可以阅读。
#include<bits/stdc++.h>
using namespace std;
const int maxn=(1<<12)+10;
const int INF=60000000;
int n,m,mp[15][15],lg[maxn],dp[13][maxn];
int mem[15],pos[maxn],tem[maxn],com[maxn];
void init()
{
for(int i=0;i<n;i++) for(int j=0;j<n;j++) mp[i][j]=INF;
for(int i=0;i<n;i++) lg[1<<i]=i;
memset(dp,0x3f,sizeof(dp));
for(int i=0;i<n;i++) dp[0][1<<i]=0;
}
inline int lowbit(int t){return t&(-t);}
int main()
{
freopen("lg3959.in","r",stdin);
freopen("lg3959.out","w",stdout);
int x,y,z,ans=INF;
scanf("%d%d",&n,&m);
init();
for(int i=1;i<=m;i++){
scanf("%d%d%d",&x,&y,&z);
x--,y--;
mp[x][y]=mp[y][x]=min(mp[x][y],z);
}
for(int i=0;i<n;i++)//深度
for(int con=0;con<(1<<n);con++){//condition
int cnt=0;//记录其补集的大小
for(int now=0;now<n;now++)
if(!(con&(1<<now))){//枚举当前状态补集
mem[cnt]=INF,pos[cnt]=1<<now;//记录当前点位置
for(int j=con;j;j-=lowbit(j)){//枚举集合
int nxt=lg[lowbit(j)];//提取枚举点位置
mem[cnt]=min(mem[cnt],mp[now][nxt]*(i+1));
//记录补集至集合连边最小代价
}
cnt++;
}
for(int j=1;j<(1<<cnt);j++){//枚举其所有补集
//这里只枚举状态数,补集由下第二个公式计算
tem[j]=tem[j-lowbit(j)]+mem[lg[lowbit(j)]];//计算当前补集代价
//由于循环方向,tem[j-lowbit(j)]都已经计算,可以递推
com[j]=com[j-lowbit(j)]|pos[lg[lowbit(j)]];//同理递推补集
dp[i+1][con|com[j]]=min(dp[i+1][con|com[j]],dp[i][con]+tem[j]);
//dp方程,将x与其补集并集,比较大小
}
}
for(int i=0;i<=n;i++) ans=min(ans,dp[i][(1<<n)-1]);
/*for(int i=0;i<n;i++){
for(int j=0;j<(1<<n);j++)
if(dp[i][j]==0x3f3f3f3f) printf("0 ");
else printf("%d ",dp[i][j]);
printf("\n");
}*/
printf("%d",ans);
return 0;
}