题面
参与考古挖掘的小明得到了一份藏宝图,藏宝图上标出了 n 个深埋在地下的宝藏屋,也给出了这 n 个宝藏屋之间可供开发的 m 条道路和它们的长度。
小明决心亲自前往挖掘所有宝藏屋中的宝藏。但是,每个宝藏屋距离地面都很远,也就是说,从地面打通一条到某个宝藏屋的道路是很困难的,而开发宝藏屋之间的道路则相对容易很多。
小明的决心感动了考古挖掘的赞助商,赞助商决定免费赞助他打通一条从地面到某个宝藏屋的通道,通往哪个宝藏屋则由小明来决定。
在此基础上,小明还需要考虑如何开凿宝藏屋之间的道路。已经开凿出的道路可以任意通行不消耗代价。每开凿出一条新道路,小明就会与考古队一起挖掘出由该条道路所能到达的宝藏屋的宝藏。另外,小明不想开发无用道路,即两个已经被挖掘过的宝藏屋之间的道路无需再开发。
新开发一条道路的代价是:
这条道路的长度 × 从赞助商帮你打通的宝藏屋到这条道路起点的宝藏屋所经过的宝藏屋的数量(包括赞助商帮你打通的宝藏屋和这条道路起点的宝藏屋)。
请你编写程序为小明选定由赞助商打通的宝藏屋和之后开凿的道路,使得工程总代价最小,并输出这个最小值。
- 对于20%的数据:
- 保证输入是一棵树,1≤n≤8,v≤5000 且所有的 v 都相等。
- 对于40%的数据:
- 1≤n≤8,0≤m≤1000,v≤5000 且所有的v 都相等。
- 对于70%的数据:
- 1≤n≤8,0≤m≤1000,v≤5000
- 对于100%的数据:
- 1≤n≤12,0≤m≤1000,v≤50000
分析
终于填了这个坑,心情愉悦啊。
状压dp,似乎也不很难想诶。
定义f[i][s]为以i为起点状态为s的最小值,然而根据题意,i任意可选择的
所以i这一维在转移的时候基本用不到,而且还有一个限制就是,两个点之间只能连一条边
所以很多状态其实是不属于子状态的,因此我们可以把f压成一维,然后dfs枚举起点,只有dfs过程中扩展到了的状态才会进入下一层搜索,这样也避免了枚举所有子集。
除此之外,还需要记录一个从起点到当前节点i经过的节点数d[i]
转移方程 (从i->j节点,s为初状态,k为末状态) f[k]=min(f[s]+(d[j]-1)*e[i][j])
代码
#include<bits/stdc++.h> using namespace std; #define N 13 #define INF 1061109567 int n,m,ans=INF; int e[N][N]; int d[N],f[1<<N]; void dfs(int s) { for(int i=1;i<=n;i++) { if(!(s&(1<<(i-1))))continue; for(int j=1;j<=n;j++) { if(s&(1<<(j-1)))continue; if(e[i][j]==INF)continue; if(f[s|(1<<(j-1))]>f[s]+d[i]*e[i][j]) { f[s|(1<<(j-1))]=f[s]+d[i]*e[i][j]; d[j]=d[i]+1; dfs(s|(1<<(j-1))); } } } } int main() { scanf("%d%d",&n,&m); memset(e,0x3f,sizeof(e)); for(int i=1;i<=m;i++) { int u,v,w; scanf("%d%d%d",&u,&v,&w); e[u][v]=e[v][u]=min(e[v][u],w); } for(int i=1;i<=n;i++) { memset(f,0x3f,sizeof(f)); memset(d,0,sizeof(d)); f[1<<(i-1)]=0; d[i]=1; dfs(1<<(i-1)); ans=min(ans,f[(1<<n)-1]); } printf("%d ",ans); return 0; }