「NOIP2017」宝藏 题解
博客阅读效果更佳
又到了一年一度NOIPCSP-S 赛前复习做真题的时间
于是就遇上了这道题
首先观察数据范围 \(1 \le n \le 12\) ,那么极大可能性是状压 \(\texttt{DP}\) 或者 \(\texttt{DFS}\) 爆搜
但由于这题放在了 \(\texttt{DP}\) 列表里面,于是优先考虑状压
简化题意:
从给定的 \(n\) 个点,\(m\) 条边的有重边的无向联通图中,找出一棵生成树,使得题目所求价值最小
从题目给出的建边价值来看,我们发现一条边的价值跟以下几点有关:
- 根的位置
- 当前状态下的树的高度
- 该边的长度
边的长度不能改变,根的位置并不能很好的作为 \(\texttt{DP}\) 时候的阶段,所以我们考虑以树的高度作为DP的阶段
设根的深度为1
设 \(\texttt{f[i][j]}\) 表示 当前树的高度为 \(i\) ,已经选了的点集的集合为 \(j\),那么状态转移方程即为
\[ f[i][j]=\min_{k\ \in \ j}(f[i-1][j \ \mathrm{xor} \ k]+dis[j \ \mathrm{xor} \ k][k]\cdot(i-1)) \]
其中异或操作在这里是取补集的意思,\(\texttt{dis[i][j]}\) 表示从 \(i\) 这个已选点集加上下一层将要选的 \(j\) 这个点集所需要的最小花费
那我们应该如何完善 \(\texttt{dis}\) 数组呢
先给出递推式
\[ dis[i][j]=dis[i][j \ \mathrm{xor} \ \mathrm{lowbit}(j)]+\min_{k=1}^{n\ \&\& \ (1<
其中 \(d\) 数组表示第 \(i\) 个点到 第 \(j\) 个点的道路长度(没有则为 \(\infty\) ),\(j\) 为 \(i\) 的补集的任一子集
然后从小到大枚举 \(j\) ,就能够保证顺序正确(因为 \(j \ \mathrm{xor} \ \mathrm{lowbit}(j)\) 一定比 \(j\) 要小)
因为每一次更新只涉及到一个点的更改,所以不难得出这样预处理 \(dis\) 数组的正确性
然后,这题就完了
另外还有几点需要注意的
边最好使用邻接矩阵储存,因为有重边,而且请不要将初值赋得太大,这样会导致在进行动态规划求解的同时溢出,从而导致答案错误
如果按照上面那种朴素的做法来进行求解复杂度有可能不能承受,观察发现我们枚举了许多不必要的子集,所以我们可以换一个方式:
for(int i=S;i;i=(i-1)&S)
这样的话所有的 \(i\) 就一定是 \(S\) 的子集
蒟蒻的理解:不等于 \(S\) 的 \(S\) 的子集一定在 \([0,S)\) 中
然后或运算可以求出在这当中十进制下数字最大的子集 ,设其为 \(P\),然后其余所有的十进制表示比他小的子集都在\([0,P)\) 当中,如此循环求解,自然能够得到所有的子集
关于状态的一点点优化
容易发现,当树高为 \(i\) 时,至少需要 \(i\) 个节点,所以所有状态中点的个数小于 \(i\) 的(即二进制位上 \(1\) 的个数小于 \(i\) 的),全部可以不用枚举子集,直接跳过,这对时间复杂度又有了进一步的
常数优化。 这可以通过预处理得到。
最后贴一下代码,变量名与上面提到的略有不同
#include
using namespace std;
const int maxn=12;
int d[maxn+5][maxn+5];
int g[maxn+5][(1<>n>>m;
for(int i=0;i>a>>b>>c;
--a,--b,d[a][b]=d[b][a]=min(d[a][b],c);
}
int x,S=(1<=1;--j)
{
int u=lg[q[j]&-q[j]],e=1000000;
for(int v=0;v