Luogu P3959 宝藏
图论+状压DP+贪心
首先可以发现在选边的过程中得到的总是一棵树
所以贪心地想,对于已选的点集,对于其能扩展到的节点肯定是选择消耗成本最少的一个
因为n很小,我们考虑状压DP
设$dp[i][mask]$表示以$i$号节点作为出发点即根节点,$mask$表示已选节点
因为选择路的成本跟根节点到该边的出发点的距离有关,又因为这是一棵树
那么设$f[i][mask][j]$表示在当前这颗树中,第$j$号节点的深度为多少,如果不在已选点集中则为-inf
那么可以将在已选点集可以扩展的边都处理出来,进行转移
采用刷表法
注意在转移过程中看似复杂度为$O(n^{4}*2^{n})$
但是在转移的过程中需要加入剪枝,可以很快的通过测试数据
#include <bits/stdc++.h> #define inf (int)1e9 using namespace std; const int MAXN=1000; int n,m,dp[14][MAXN*5]; int f[14][MAXN*5][14]; int mp[14][14],w; vector <int> e[14]; int main() { scanf("%d%d",&n,&m); for (int i=0;i<n;i++) { for (int j=0;j<n;j++) mp[i][j]=inf; } for (int i=1;i<=m;i++) { int u,v,l; scanf("%d%d%d",&u,&v,&l); u--;v--; mp[u][v]=min(mp[u][v],l); mp[v][u]=min(mp[v][u],l);//注意重边的情况 } int full; full=(1<<n)-1; for (int i=0;i<n;i++) { for (int mask=0;mask<=full;mask++) dp[i][mask]=inf; } for (int i=0;i<n;i++) { dp[i][1<<i]=0; f[i][1<<i][i]=1; } for (int mask=0;mask<=full;mask++) { for (int i=0;i<n;i++)//枚举由那个节点作为开始节点 { if (dp[i][mask]==inf)//剪枝 continue; for (int j=0;j<n;j++)//枚举点集中的点 { if (f[i][mask][j]!=0)//判断是否在点集中 { for (int k=0;k<n;k++)//枚举能扩展的边 { if (mp[j][k]==inf || f[i][mask][k]!=0)//剪枝 continue; if (dp[i][mask|(1<<k)]>dp[i][mask]+mp[j][k]*f[i][mask][j])//进行转移 { dp[i][mask|(1<<k)]=dp[i][mask]+mp[j][k]*f[i][mask][j]; for (int p=0;p<n;p++) f[i][mask|(1<<k)][p]=f[i][mask][p]; f[i][mask|(1<<k)][k]=f[i][mask][j]+1;//更新节点的深度 } } } } } } int ans=inf; for (int i=0;i<n;i++) ans=min(ans,dp[i][full]);//统计答案 printf("%d\n",ans); }