因为辣鸡csdn,导致之前快写好的博客没了
QWQ悲伤逆流成河qwqqq
首先虚树,这个东西,我感觉是一种思想,或者是方法,而并不是一个数据结构什么的。
他主要是用来解决:给出一棵树,每次询问选择一些关键点,求一些信息。
这些信息的特点是,许多未选择的点可以通过某种方式剔除而不影响最终结果。
于是就有了建虚树这个算法。我们根据原树的信息重新建树,这棵树中要尽量少地包含非关键节点。 这棵树就叫做虚树。这棵虚树包含任意两个关键节点的LCA。
通常这类题
会
飞,而
是能跑得过的QWQ
那我们应该怎么构造虚树呢QWQ我们把所有关键点按照dfs序排好序。
然后,虚树要有一个根。这里一般直接把1号节点设为根。构建虚树的主要过程就是使用一个栈,维护从根开始的一条链。这条链上的所有点的dfs序一定是递增的。
我们把关键点扫描一遍,一边维护这个栈一边连边构建虚树。
具体来说:
1.把根节点放入栈中2.把所有关键点点扫一遍。设当前的节点为 ,栈顶(链末端)的节点为 。求出 和 的 。
此时有2种情况。
1) 为 。此时 在 子树内。我们就把 压入栈,相当于直接把 添加到这条链的末尾。
2) 分立在 的两个子树中。此时y这个子树中的所有关键点一定都被遍历过了。(原因:设有一关键点a在y子树中,没有被遍历过。则 。但是我们是把关键点按照dfs序来排的,a一定在x之前被扫)
因此y子树内的所有关键点都已经被被加入虚树。接下来我们要把 这一段的点加入虚树。我们设栈顶的节点为 ,栈顶的第二个节点为 。
重复以下操作:
1.若 直接连边 ,然后把 出栈。
2.若 这意味着 就是 。直接连边 。此时子树已构建完毕。
3.若 ,说明 被 和 夹在中间。此时我们必须要把 加入虚树,所以连边 ,然后把 弹出栈,把lca入栈。然后子树构造完毕。
最后把栈里面的元素搞一搞,每次 即可感觉配合代码会比较好理解
void solve()
{
// memset(point,0,sizeof(point));
cnt=0;
sort(a+1,a+1+k,cmp);
top=1;
sta[top]=1;
for (int i=1;i<=k;i++)
{
int l = lca(sta[top],a[i]);
if (l!=sta[top])
{
while (top>1)
{
if (dfn[sta[top-1]]>dfn[l])
{
addedge(sta[top-1],sta[top],0);
top--;
}
else
{
if (dfn[sta[top-1]]==dfn[l])
{
addedge(sta[top-1],sta[top],0);
top--;
break;
}
else
{
addedge(l,sta[top],0);
sta[top]=l;
break;
}
}
}
}
if (sta[top]!=a[i]) sta[++top]=a[i];
}
while (top>1)
{
addedge(sta[top-1],sta[top],0);
top--;
}
}
那么剩下的主要就是 部分了
首先,我们定义 表示在原树上 的路径上边权的最小值, 表示切割完 子树内所有关键点的最小花费。
对于当前点 ,如果他是关键点,那么必须切割这个点到1的路经上的一条边,那么就是 ,否则
上代码
int dp(int x,int flag)
{
int sum=0;
for (int &i=point[x];i;i=nxt[i])
{
int p = to[i];
sum=sum+dp(p,flag);
}
if (tag[x]==flag)
{
return mn[x];
}
return min(sum,mn[x]);
}
其中有一个要注意的地方就是虚树的时候,因为每次要重新建树,所以要清空 数组,而由于时间原因,又不能直接 ,所以我们只能使用奇妙的手段!自杀式遍历通过取地址,不断修改
for (int &i=point[x];i;i=nxt[i])
由于死机了三次QWQ
所以暂时没有办法放整个题的代码