Description
参与考古挖掘的小明得到了一份藏宝图,藏宝图上标出了 n 个深埋在地下的宝藏屋,也给出了这 n 个宝藏屋之间可供开发的 m 条道路和它们的长度。
小明决心亲自前往挖掘所有宝藏屋中的宝藏。但是,每个宝藏屋距离地面都很远,也就是说,从地面打通一条到某个宝藏屋的道路是很困难的,而开发宝藏屋之间的道路则相对容易很多。
小明的决心感动了考古挖掘的赞助商,赞助商决定免费赞助他打通一条从地面到某个宝藏屋的通道,通往哪个宝藏屋则由小明来决定。
在此基础上,小明还需要考虑如何开凿宝藏屋之间的道路。已经开凿出的道路可以任意通行不消耗代价。每开凿出一条新道路,小明就会与考古队一起挖掘出由该条道路所能到达的宝藏屋的宝藏。另外,小明不想开发无用道路,即两个已经被挖掘过的宝藏屋之间的道路无需再开发。
新开发一条道路的代价是:
这条道路的长度 × 从赞助商帮你打通的宝藏屋到这条道路起点的宝藏屋所经过的宝藏屋的数量(包括赞助商帮你打通的宝藏屋和这条道路起点的宝藏屋)。
请你编写程序为小明选定由赞助商打通的宝藏屋和之后开凿的道路,使得工程总代价最小,并输出这个最小值。
Input
第一行两个用空格分离的正整数 n 和 m,代表宝藏屋的个数和道路数。
接下来 m 行,每行三个用空格分离的正整数,分别是由一条道路连接的两个宝藏屋的编号(编号为 1~n),和这条道路的长度 v。
Output
输出共一行,一个正整数,表示最小的总代价。
Sample Input
【输入样例 1】
4 5
1 2 1
1 3 3
1 4 1
2 3 4
3 4 1
【样例输入 2】
4 5
1 2 1
1 3 3
1 4 1
2 3 4
3 4 2
Sample Output
【输出样例 1】
4
【输入输出样例 1 说明】
小明选定让赞助商打通了 1 号宝藏屋。小明开发了道路 1->2,挖掘了 2 号宝藏。开发了道路 1->4,挖掘了 4 号宝藏。还开发了道路 4->3,挖掘了 3 号宝藏。工程总代价为:1 × 1 + 1 × 1 + 1 × 2 = 4
(1->2) (1->4) (4->3)
【样例输出 2】
5
【输入输出样例 2 说明】
小明选定让赞助商打通了 1 号宝藏屋。小明开发了道路 1->2,挖掘了 2 号宝藏。开发了道路 1->3,挖掘了 3 号宝藏。还开发了道路 1->4,挖掘了 4 号宝藏。工程总代价为:1 × 1 + 3 × 1 + 1 × 1 = 5
(1->2) (1->3) (1->4)
Data Constraint
对于 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≤ 500000
Solution
这道题有两个解法:搜索剪枝(再加估值函数等神奇优化),状压DP。
速度似乎搜索更快,但状压DP的时间复杂度更稳定。
看到 n≤12 ,自然就想到状压DP。
设 F[i][s] 表示最大深度为 i 、当前选了的点的集合为
s 的最小总代价。再设 c[s][s1] 表示当前选了集合 s ,用这个集合连出去,直到选完补集
s1 的所选的边的最小值。由于直接求不好求,而且 n 奇小,可以尽情地预处理。
设 h[s][i] 表示当前选了集合为 s 的点,要连接
i 号点的边的最小值。那么容易处理出 h 数组,则就可以简单地处理出
c 数组了。为了更快地枚举集合 s 的子集,我们还可以设一个
g[s][i] 表示其第 i 个补集。同样的,
g 也能快速地预处理出来。那么我们就可以愉快地DP了。由于深度小的更优解会在之前就计算出来,这样做的正确性显然。
注意处理 c 数组时,若两集合不能连接(其中一个元素不能连接即说明整体连不了),
要将值赋为最大值,不然会将答案算小。
时间复杂度
O(22n∗n) ,轻松过掉。
Code
#include<cstdio>
#include<cstring>
#include<cctype>
using namespace std;
const int N=13,M=(1<<N-1)+5,inf=1e9;
int a[N][N],p[N],f[N][M],g[M][M],h[M][N],c[M][M];
inline int read()
{
int X=0,w=0; char ch=0;
while(!isdigit(ch)) {w|=ch=='-';ch=getchar();}
while(isdigit(ch)) X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
return w?-X:X;
}
inline int min(int x,int y)
{
return x<y?x:y;
}
int main()
{
int n=read(),m=read();
memset(a,60,sizeof(a));
for(int i=1;i<=m;i++)
{
int x=read(),y=read(),z=read();
if(z<a[x][y]) a[y][x]=a[x][y]=z;
}
for(int i=p[0]=1;i<N;i++) p[i]=p[i-1]<<1;
for(int i=0;i<p[n];i++)
for(int j=1;j<p[n];j++)
if(!(i&j)) g[i][++g[i][0]]=j;
memset(h,60,sizeof(h));
for(int i=1;i<p[n];i++)
for(int j=1;j<=n;j++)
if(!(i&p[j-1]))
for(int k=1;k<=n;k++)
if(i&p[k-1]) h[i][j]=min(h[i][j],a[k][j]);
for(int i=1;i<p[n];i++)
for(int j=1;j<=g[i][0];j++)
for(int k=1;k<=n;k++)
if(p[k-1]&g[i][j])
if(h[i][k]<inf) c[i][g[i][j]]+=h[i][k]; else
{
c[i][g[i][j]]=inf;
break;
}
memset(f,60,sizeof(f));
for(int i=1;i<=n;i++) f[0][p[i-1]]=0;
for(int i=0;i<n;i++)
for(int j=0;j<p[n];j++)
for(int k=1;k<=g[j][0];k++)
if(f[i][j]<inf && c[j][g[j][k]]<inf)
f[i+1][j|g[j][k]]=min(f[i+1][j|g[j][k]],f[i][j]+c[j][g[j][k]]*(i+1));
int ans=inf;
for(int i=0;i<=n;i++) ans=min(ans,f[i][p[n]-1]);
printf("%d",ans);
return 0;
}