虚树学习笔记(洛谷2495 消耗战)
因为辣鸡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
所以暂时没有办法放整个题的代码