分析:
首先我们需要明确,最后我们打通的道路组成的一定是一个树形结构
一个朴素的想法是这样:
我们如果有一棵生成树,现在要往里面加入一个点
那么贪心的加边即可(枚举该点到生成树每一个点的连边)
当然上述做法显然有纰漏,如果这个点的最优路径端点不在生成树中,就不对了
但是如果给定一个加点序列,我们会发现上述贪心得出的是该排列下的最优解
所以枚举全排列,之后贪心即可
时间复杂度O(n!*n^2),会T4个点
那么仔细的看一下,其实贪心不仅是该选择排列下的最优解
还是深度序列下的最优解(每个节点在dfs树上都有一个深度)
我们枚举的全排列很多,但是对应的深度序列却很少
所以我们可以从深度考虑
这就是这道题的精髓所在了
n<=12,很符合状压dp的套路(不知道为什么CCF钟爱状压)
令f[i][S]表示当前与根连通的点的状态为S,并且最深的点的深度为i的最小代价
这样下一层的点就一定在S的补集SS中
我们枚举SS的所有子集K,作为下一层的点集,
处理出K中每个点与S中的某个点连通所需要的最小代价
用K中所有点的代价+f[i][S]去更新f[i+1][S|K]即可
时间复杂度O(n3^n)
tip
这道题的思路确实清奇一点
注意f的初始化
//这里写代码片
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
const int INF=1e9;
int G[20][20],f[20][4100];
int n,m,tot,ans;
int main()
{
scanf("%d%d",&n,&m);
for (int i=0;i<n;i++)
for (int j=0;j<n;j++) G[i][j]=INF;
for (int i=1;i<=m;i++)
{
int u,w,z;
scanf("%d%d%d",&u,&w,&z);
u--;w--;
G[u][w]=min(G[u][w],z);
G[w][u]=G[u][w];
}
for (int i=0;i<n;i++) for (int j=0;j<(1<<n);j++) f[i][j]=INF;
for (int i=0;i<n;i++) f[0][1<<i]=0; //边界条件
int i,j,k,x,a,b,xx;
for (i=0;i<n;i++) //最深深度
for (x=0;x<(1<<n);x++) //S
if (f[i][x]!=INF) //该状态存在
{
xx=x^((1<<n)-1); //S的补集SS
for (k=xx;k;k=(k-1)&xx) //枚举SS的子集K
{
int rep=0; //总花费
for (a=0;a<n;a++) //贪心
if (k&(1<<a)) //a在K中
{
int v=INF; //a连到生成树上的最小代价
for (b=0;b<n;b++) if (x&(1<<b)) //枚举状态S中的点
v=min(v,G[a][b]);
if (v==INF) goto ed;
rep+=v;
}
rep*=(i+1); //乘以深度(i从0枚举的)
f[i+1][x|k]=min(f[i+1][x|k],f[i][x]+rep);
ed:;
}
}
ans=INF;
for (i=0;i<n;i++) ans=min(ans,f[i][(1<<n)-1]);
printf("%d",ans);
return 0;
}

本文介绍了一种利用状态压缩动态规划(状压DP)解决特定道路生成树问题的方法。通过分析问题特点,采用枚举深度序列而非全排列的方式优化贪心策略,实现了时间复杂度为O(n³ⁿ)的高效算法。
1314

被折叠的 条评论
为什么被折叠?



