【杂题总结】洛谷-3959 宝藏
【洛谷-3959】 宝藏
感觉NOIP2107提高组的题只写第一道水题不太合适🤔然后再写一道……线下考试的时候脑子瓦特了,状压都写了就是没写出正解QwQ
◇ 题目(copy from 洛谷)
题目描述
参与考古挖掘的小明得到了一份藏宝图,藏宝图上标出了 n 个深埋在地下的宝藏屋, 也给出了这 n 个宝藏屋之间可供开发的 m 条道路和它们的长度。
小明决心亲自前往挖掘所有宝藏屋中的宝藏。但是,每个宝藏屋距离地面都很远, 也就是说,从地面打通一条到某个宝藏屋的道路是很困难的,而开发宝藏屋之间的道路 则相对容易很多。
小明的决心感动了考古挖掘的赞助商,赞助商决定免费赞助他打通一条从地面到某 个宝藏屋的通道,通往哪个宝藏屋则由小明来决定。
在此基础上,小明还需要考虑如何开凿宝藏屋之间的道路。已经开凿出的道路可以 任意通行不消耗代价。每开凿出一条新道路,小明就会与考古队一起挖掘出由该条道路 所能到达的宝藏屋的宝藏。另外,小明不想开发无用道路,即两个已经被挖掘过的宝藏 屋之间的道路无需再开发。
新开发一条道路的代价是:L*K
L代表这条道路的长度,K代表从赞助商帮你打通的宝藏屋到这条道路起点的宝藏屋所经过的 宝藏屋的数量(包括赞助商帮你打通的宝藏屋和这条道路起点的宝藏屋) 。请你编写程序为小明选定由赞助商打通的宝藏屋和之后开凿的道路,使得工程总代 价最小,并输出这个最小值。
输入输出格式
输入格式:
第一行两个用空格分离的正整数 n,m,代表宝藏屋的个数和道路数。
接下来 m 行,每行三个用空格分离的正整数,分别是由一条道路连接的两个宝藏 屋的编号(编号为 1−n),和这条道路的长度 v。输出格式:
一个正整数,表示最小的总代价。
◇ 解析
看到n的数据规模就大概知道是状压了(因为2^12=4096并不大,状压比较合适,也算是一个小技巧吧)。
根据贪心的思想,只要所有宝藏都连通就可以了,所以只需要生成一棵树!但是这棵生成树非常的奇妙……不仅是有根树,而且根的位置还会影响权值,这就是为什么不能直接生成树。那么状压就开始有用了🙃:dp[S] 表示当前生成树的点集为S时的最小花费。
因为最后生成树的根会对答案产生直接影响,所以我们需要先枚举点i作为根。那么生成树的初始状态就只包含 i 这一个根节点,也就是(1<<i),此时没有边,权值也就是0。然后根据样例我们容易发现一条边对于最后权值的贡献为 它的起点的深度×边的长度 (根节点深度为1),所以我们需要计算一下点的深度 dep[]。
话不多说,先把状态转移方程列出来,再解释也不迟😊:
dp[S] = min{ dp[S|(1<<v)] + dep[u] * lnk[u][v] } (u属于S,v不属于S)
从原来的状态S拓展到(S+v),即从属于S的一个点u扩展到v,则增加的是起点u的深度dep[u]乘上u到v的距离。
接下来就很简单了——先枚举一个树的根,并把它的深度定为1。现在树只有一个节点,从这个状态开始转移:先枚举当前状态中的一个点u,再枚举一个不属于当前状态的点v,扩展,最后扩展到包含所有点。
◇ 解析
/*Lucky_Glass*/ #include<cstdio> #include<cstring> #include<algorithm> using namespace std; inline bool insid(int a,int b) {return ((1<<a)&b);} const int N=12; int n,m,INF; int lnk[N+7][N+7],dp[(1<<N)+7],dep[N+7]; void DFS(int S){ for(int u=0;u<n;u++) if(insid(u,S)) for(int v=0;v<n;v++) if(!insid(v,S) && lnk[u][v]<INF){ int s=S|(1<<v); if(dp[s]>dp[S]+lnk[u][v]*dep[u]){ dp[s]=dp[S]+lnk[u][v]*dep[u]; int las=dep[v]; dep[v]=dep[u]+1; DFS(s); dep[v]=las; } } } int main(){ //freopen("treasure.in","r",stdin); //freopen("treasure.out","w",stdout); memset(lnk,0x3f,sizeof lnk);INF=lnk[0][0]; scanf("%d%d",&n,&m); for(int i=0;i<m;i++){ int u,v,l; scanf("%d%d%d",&u,&v,&l);u--;v--; lnk[u][v]=lnk[v][u]=min(lnk[u][v],l); } int ans=INF; for(int i=0;i<n;i++){ memset(dep,0x3f,sizeof dep); memset(dp,0x3f,sizeof dp); dep[i]=1;dp[1<<i]=0; DFS(1<<i); ans=min(ans,dp[(1<<n)-1]); } printf("%d\n",ans); return 0; }
The End
Thanks for reading!
- Lucky_Glass