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