P3959 [NOIP2017 提高组] 宝藏
P3959 [NOIP2017 提高组] 宝藏
题目描述
参与考古挖掘的小明得到了一份藏宝图,藏宝图上标出了 \(n\) 个深埋在地下的宝藏屋, 也给出了这 \(n\) 个宝藏屋之间可供开发的 \(m\) 条道路和它们的长度。
小明决心亲自前往挖掘所有宝藏屋中的宝藏。但是,每个宝藏屋距离地面都很远,也就是说,从地面打通一条到某个宝藏屋的道路是很困难的,而开发宝藏屋之间的道路则相对容易很多。
小明的决心感动了考古挖掘的赞助商,赞助商决定免费赞助他打通一条从地面到某个宝藏屋的通道,通往哪个宝藏屋则由小明来决定。
在此基础上,小明还需要考虑如何开凿宝藏屋之间的道路。已经开凿出的道路可以 任意通行不消耗代价。每开凿出一条新道路,小明就会与考古队一起挖掘出由该条道路所能到达的宝藏屋的宝藏。另外,小明不想开发无用道路,即两个已经被挖掘过的宝藏屋之间的道路无需再开发。
新开发一条道路的代价是 \(\mathrm{L} \times \mathrm{K}\)。其中 \(L\) 代表这条道路的长度,\(K\) 代表从赞助商帮你打通的宝藏屋到这条道路起点的宝藏屋所经过的宝藏屋的数量(包括赞助商帮你打通的宝藏屋和这条道路起点的宝藏屋) 。
请你编写程序为小明选定由赞助商打通的宝藏屋和之后开凿的道路,使得工程总代价最小,并输出这个最小值。
输入格式
第一行两个用空格分离的正整数 \(n,m\),代表宝藏屋的个数和道路数。
接下来 \(m\) 行,每行三个用空格分离的正整数,分别是由一条道路连接的两个宝藏屋的编号(编号为 \(1-n\)),和这条道路的长度 \(v\)。
输出格式
一个正整数,表示最小的总代价。
样例 #1
样例输入 #1
4 5
1 2 1
1 3 3
1 4 1
2 3 4
3 4 1
样例输出 #1
4
样例 #2
样例输入 #2
4 5
1 2 1
1 3 3
1 4 1
2 3 4
3 4 2
样例输出 #2
5
提示
【样例解释 \(1\)】
小明选定让赞助商打通了 \(1\) 号宝藏屋。小明开发了道路 \(1 \to 2\),挖掘了 \(2\) 号宝藏。开发了道路 \(1 \to 4\),挖掘了 \(4\) 号宝藏。还开发了道路 \(4 \to 3\),挖掘了 \(3\) 号宝藏。
工程总代价为 \(1 \times 1 + 1 \times 1 + 1 \times 2 = 4\)。
【样例解释 \(2\)】
小明选定让赞助商打通了 \(1\) 号宝藏屋。小明开发了道路 \(1 \to 2\),挖掘了 \(2\) 号宝藏。开发了道路 \(1 \to 3\),挖掘了 \(3\) 号宝藏。还开发了道路 \(1 \to 4\),挖掘了 \(4\) 号宝藏。
工程总代价为 \(1 \times 1 + 3 \times 1 + 1 \times 1 = 5\)。
【数据规模与约定】
对于 \(20\%\) 的数据: 保证输入是一棵树,\(1 \le n \le 8\),\(v \le 5\times 10^3\) 且所有的 \(v\) 都相等。
对于 \(40\%\) 的数据: \(1 \le n \le 8\),\(0 \le m \le 10^3\),\(v \le 5\times 10^3\) 且所有的 \(v\) 都相等。
对于 \(70\%\) 的数据: \(1 \le n \le 8\),\(0 \le m \le 10^3\),\(v \le 5\times 10^3\)。
对于 \(100\%\) 的数据: \(1 \le n \le 12\),\(0 \le m \le 10^3\),\(v \le 5\times 10^5\)。
Solution
首先看到数据范围中 \(n\le 12\) ,大概可以猜到是一道状压题。另外,数据是一个无向图,并且 \(n \le 12\) ,但是 \(m\le 1000\) ,这说明会有重边的情况产生,所以在输入时需要进行判断,在重边中取得一个最小的边权存储。
然后考虑状态压缩得到的数据可以如何使用。显然, \(n\) 对应的是无向图的点,因此如果对点进行二进制压缩,存储的信息就应该是对应位置上的点是否被打通。那么由此可以设出 \(f[i]\) 表示当状态为 \(i\) 时的最小代价。但是容易发现,因为在当前位置在生成树上的深度不同时,可能会具有后效性,导致答案错误,因此我们在状态中多存储一个在生成树上的深度来避免产生后效性,即设 \(f[i][j]\) 表示当前在深度 \(i\) 时状态为 \(j\) 的最小代价,那么可以推出状态转移方程:\(f[i][j]=min\{f[i-1][k]+val[k][j]*(i-1)\}\) , \(val[k][j]\) 表示从状态 \(k\) 变化到状态 \(j\) 所要付出的路程的代价,乘上 \((i-1)\) 即题目中要求的 \(K\) (离生成树树根的距离,即深度)。
现在来看如何预处理出 \(val[i][j]\) 数组。很明显,状态的总数只有 \(4000\) 上下,完全可以直接暴力枚举计算。枚举 \(i\) ,那么状态 \(i\) 在不考虑连通性的情况下来源只可能为 \(i\) 的子集,那么 \(j\) 只需要枚举 \(i\) 的子集即可,然后直接对于 \(i,j\) 不同的部分判断联通,并加边计算即可。
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define mem(a,b) memset(a,b,sizeof(a));
#define INTMAX 0x3f3f3f3f
#define LLONGMAX 0x3f3f3f3f3f3f3f3fll
using namespace std;
template<typename T> void read(T &k)
{
k=0;
T flag=1;char b=getchar();
while (b<'0' || b>'9') {flag=(b=='-')?-1:1;b=getchar();}
while (b>='0' && b<='9') {k=(k<<3)+(k<<1)+(b^48);b=getchar();}
k*=flag;
}
const int _STATSIZE=(1<<12);
int n,m;
int dis[17][17],val[_STATSIZE+5][_STATSIZE+5];
long long f[17][_STATSIZE+5],ans=LLONGMAX;
int main()
{
read(n),read(m);
mem(dis,0x3f);
for (int i=1;i<=m;i++)
{
int u,v,len;
read(u),read(v),read(len);
dis[u][v]=dis[v][u]=min(dis[u][v],len);
}
//for (int i=1;i<=n;i++) for (int j=i+1;j<=n;j++) printf("%d~%d:%d\n",i,j,dis[i][j]);
for (int i=0;i<=(1<<n)-1;i++)
for (int j=i;j;j=(j-1)&i)
{
bool flag=0;
int temp=i^j;
for (int k=n-1;k>=0;k--)
if (temp>=(1<<k))
{
int minn=INTMAX;
for (int l=0;l<n;l++)
if (((1<<l)&j)==(1<<l)) minn=min(minn,dis[l+1][k+1]);
if (minn==INTMAX) {flag=1;break;}
val[j][i]+=minn;
temp-=(1<<k);
}
if (flag) val[j][i]=INTMAX;
}
//for (int i=0;i<=(1<<n)-1;i++) for (int j=0;j<=(1<<n)-1;j++) printf("%d~%d:%d\n",i,j,val[i][j]);
mem(f,0x3f);
for (int i=0;i<n;i++)
f[1][(1<<i)]=0;
for (int i=2;i<=n;i++)
for (int j=0;j<=(1<<n)-1;j++)
for (int k=j;k;k=(k-1)&j)
if (val[k][j]!=INTMAX) f[i][j]=min(f[i-1][k]+val[k][j]*(i-1),f[i][j]);
for (int i=1;i<=n;i++)
ans=min(ans,f[i][(1<<n)-1]);
printf("%lld\n",ans);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步