我们发现,如果一棵树中真正需要处理的点很少,而总共点数很多时,可以只处理那些需要的点,而忽略其他点。

因此我们可以根据那些需要的点构建虚树,只保留关键点。

oi-wiki上对虚树的介绍

我们根据一下方式建立虚树:

  • 现将所有需要处理的关键按照欧拉序排序。

  • 用一个栈维护一条从根节点到上一个处理过个点的链(虚树上的链)。

  • 考虑将一个新的点加入虚树:

    • 求出这个点与栈顶元素的 \(Lca\) 。

    • 如果 \(Lca\) 不是栈顶元素:

      • 在栈中只保留栈中的链与现在加入点所在的链的公共部分加上一个上一次处理完的链中元素(通过 \(Lca\) 的 \(dfn\) )。

      • 如果 \(Lca\) 已经在栈中,则弹出那个多余的元素。

      • 如果 \(Lca\) 还不在栈中,则将 \(Lca\) 与多余元素连边,并加入 \(Lca\) 。

    • 把新的点加入栈中。

    • 处理完后把栈中的链也连边连上。

注意:由于整个图需要用到的边与点很少,所以在每次新建虚树的时候不能全局清空,而是在把一个新的点加入栈中的时候清空这个点连过的边。

建立虚树代码:

inline void build()
{
sort(query+1,query+m+1,cmp),tot=tp=0;
sta[++tp]=1,hea[1]=0;
for(int i=1,l;i<=m;i++)
{
if(query[i]==1) continue;
l=Lca(query[i],sta[tp]);
if(l!=sta[tp])
{
while(dfn[l]<dfn[sta[tp-1]])
{
Lca(sta[tp-1],sta[tp]); // 这里用来处理这一条虚树中的边的权值。
add(sta[tp-1],sta[tp],minn);
tp--;
}
if(sta[tp-1]!=l)
{
hea[l]=0;
Lca(l,sta[tp]); // 这里用来处理这一条虚树中的边的权值。
add(l,sta[tp],minn);
sta[tp]=l;
}
else
{
Lca(l,sta[tp]); // 这里用来处理这一条虚树中的边的权值。
add(l,sta[tp--],minn);
}
}
hea[query[i]]=0,sta[++tp]=query[i];
}
for(int i=1;i<tp;i++)
{
Lca(sta[i],sta[i+1]); // 这里用来处理这一条虚树中的边的权值。
add(sta[i],sta[i+1],minn);
}
}

建立完之后就可以在虚树上处理答案了,这样即使多次询问,复杂度也是和选中关键点个数同阶的。

例题:

世界树 $-\texttt{code}$
// Author:A weak man named EricQian
#include<bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
#define Maxn 300005
#define Maxpown 21
#define pb push_back
#define pa pair<int,int>
#define fi first
#define se second
typedef long long ll;
inline int rd()
{
int x=0;
char ch,t=0;
while(!isdigit(ch = getchar())) t|=ch=='-';
while(isdigit(ch)) x=x*10+(ch^48),ch=getchar();
return x=t?-x:x;
}
int n,m,q,tot,Time,tp;
struct Dot{ int num,pointnum; }query[Maxn];
int dep[Maxn],siz[Maxn],dfn[Maxn],sta[Maxn],fa[Maxn][Maxpown]; // 处理虚树
bool exist[Maxn]; // 处理虚树
int ans[Maxn],belson[Maxn],hea[Maxn],nex[Maxn],ver[Maxn],edg[Maxn]; // 虚树
// belson[v] : u 是 v 在 虚树上的父亲,belson 是 v 属于 u 的那一个原图上的儿子
pa Near[Maxn];
vector<int> g[Maxn]; // 原图
void dfspre(int x)
{
dfn[x]=++Time,siz[x]=1;
for(int v:g[x])
{
if(v==fa[x][0]) continue;
fa[v][0]=x,dep[v]=dep[x]+1;
for(int i=1;i<=20;i++) fa[v][i]=fa[fa[v][i-1]][i-1];
dfspre(v);
siz[x]+=siz[v];
}
}
inline int Lca(int x,int y)
{
if(dep[x]>dep[y]) swap(x,y);
for(int i=20;i>=0;i--) if(dep[fa[y][i]]>=dep[x]) y=fa[y][i];
if(x==y) return x;
for(int i=20;i>=0;i--) if(fa[x][i]!=fa[y][i])
x=fa[x][i],y=fa[y][i];
return fa[x][0];
}
inline void add(int x,int y,int d){ ver[++tot]=y,nex[tot]=hea[x],hea[x]=tot,edg[tot]=d; }
bool cmp1(Dot x,Dot y){ return dfn[x.pointnum]<dfn[y.pointnum]; }
bool cmp2(Dot x,Dot y){ return x.num<y.num; }
inline void build()
{
m=rd();
for(int i=1;i<=m;i++) query[i]=(Dot){i,rd()},exist[query[i].pointnum]=true;
sort(query+1,query+m+1,cmp1);
tot=0,sta[tp=1]=1,hea[1]=0;
for(int i=1,l;i<=m;i++)
{
if(query[i].pointnum==1) continue;
l=Lca(sta[tp],query[i].pointnum);
if(l!=sta[tp])
{
while(dfn[l]<dfn[sta[tp-1]])
add(sta[tp-1],sta[tp],dep[sta[tp]]-dep[sta[tp-1]]),tp--;
if(sta[tp-1]!=l)
hea[l]=0,add(l,sta[tp],dep[sta[tp]]-dep[l]),sta[tp]=l;
else add(l,sta[tp],dep[sta[tp]]-dep[l]),tp--;
}
hea[query[i].pointnum]=0,sta[++tp]=query[i].pointnum;
}
for(int i=1;i<tp;i++) add(sta[i],sta[i+1],dep[sta[i+1]]-dep[sta[i]]);
}
void dfs1(int x) // 处理最近的特殊点-1 (+init)
{
if(exist[x]) Near[x]=pa(0,x);
else Near[x]=pa(inf,inf);
ans[x]=0;
for(int i=hea[x],tmp,Now;i;i=nex[i])
{
dfs1(ver[i]);
Now=Near[x].fi,tmp=Near[ver[i]].fi+edg[i];
if((tmp<Now) || (tmp==Now && Near[ver[i]].se<Near[x].se))
Near[x].fi=tmp,Near[x].se=Near[ver[i]].se;
}
}
void dfs2(int x) // 处理最近的特殊点-2
{
for(int i=hea[x],Now,tmp;i;i=nex[i])
{
Now=Near[x].fi+edg[i],tmp=Near[ver[i]].fi;
if((Now<tmp) || (Now==tmp && Near[x].se<Near[ver[i]].se))
Near[ver[i]].fi=Now,Near[ver[i]].se=Near[x].se;
dfs2(ver[i]);
}
}
void dfs3(int x) // 把在虚树外的点计算(通过倍增到父亲的下面)
{
int All=siz[x];
for(int i=hea[x],tmp;i;i=nex[i])
{
tmp=ver[i];
for(int j=20;j>=0;j--) if(dep[fa[tmp][j]]>dep[x]) tmp=fa[tmp][j];
All-=siz[tmp],belson[ver[i]]=tmp;
dfs3(ver[i]);
}
ans[Near[x].se]+=All;
}
void dfs4(int x) // 计算中间的点
{
for(int i=hea[x];i;i=nex[i])
{
if(Near[x].se==Near[ver[i]].se)
ans[Near[x].se]+=siz[belson[ver[i]]]-siz[ver[i]];
else
{
int downdep=dep[Near[ver[i]].se]+dep[x]-Near[x].fi;
if(downdep & 1) downdep=downdep/2+1;
else downdep=(Near[x].se<Near[ver[i]].se)?(downdep/2+1):(downdep/2);
int tmp=ver[i];
for(int j=20;j>=0;j--) if(dep[fa[tmp][j]]>=downdep) tmp=fa[tmp][j];
ans[Near[x].se]+=siz[belson[ver[i]]]-siz[tmp];
ans[Near[ver[i]].se]+=siz[tmp]-siz[ver[i]];
}
dfs4(ver[i]);
}
}
int main()
{
//ios::sync_with_stdio(false); cin.tie(0);
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
n=rd();
for(int i=1,x,y;i<n;i++) x=rd(),y=rd(),g[x].pb(y),g[y].pb(x);
dep[1]=1,dfspre(1),q=rd();
for(int i=1;i<=q;i++)
{
build();
dfs1(1),dfs2(1); // 处理好最近的点
dfs3(1),dfs4(1); // 计算答案
sort(query+1,query+m+1,cmp2);
for(int j=1;j<=m;j++) printf("%d%c",ans[query[j].pointnum],(j==m)?'\n':' ');
for(int j=1;j<=m;j++) exist[query[j].pointnum]=false;
}
//fclose(stdin);
//fclose(stdout);
return 0;
}

虚树 virtual-tree的更多相关文章

  1. HDU-6035:Colorful Tree(虚树+DP)

    这里有三道长得像的题: 一:HDU6036: There is a tree with nn nodes, each of which has a type of color represented ...

  2. 【虚树】hdu6161 Big binary tree

    题意:一棵n个结点的完全二叉树,初始i号结点的权值为i.有两种操作:单点修改:询问经过某个结点的路径中,权值和最大的路径的权值和是多少. 修改的时候,暴力修改到根节点的路径上的点的f(x)即可. 跟虚 ...

  3. Codeforces 1111 E. Tree(虚树,DP)

    题意 有一棵树,q个询问,每次询问,指定一个点做树根,再给定k个点,要求把这些点分成不超过m组的方案数,分配的限制是任意两个有祖先关系的点不能分在同一组.题目还保证了所有的询问的k加起来不超过1e5. ...

  4. hdu 6035 Colorful Tree(虚树)

    考虑到树上操作:首先题目要我们求每条路径上出现不同颜色的数量,并把所有加起来得到答案:我们知道俩俩点之间会形成一条路径,所以我们可以知道每个样例的总的路径的数目为:n*(n-1)/2: 这样单单的求, ...

  5. 2020牛客NOIP赛前集训营-提高组(第三场)C-牛半仙的妹子Tree【虚树,最短路】

    正题 题目链接:https://ac.nowcoder.com/acm/contest/7609/C 题目大意 给出\(n\)个点的一棵树,\(m\)个时刻各有一个操作 标记一个点,每个点被标记后的每 ...

  6. Codeforces 1606F - Tree Queries(虚树+树形 dp)

    Codeforces 题面传送门 & 洛谷题面传送门 显然我们选择删除的点连同 \(u\) 会形成一个连通块,否则我们如果选择不删除不与 \(u\) 在同一连通块中的点,答案一定更优. 注意到 ...

  7. 青云的机房组网方案(简单+普通+困难)(虚树+树形DP+容斥)

    题目链接 1.对于简单的版本n<=500, ai<=50 直接暴力枚举两个点x,y,dfs求x与y的距离. 2.对于普通难度n<=10000,ai<=500 普通难度解法挺多 ...

  8. BZOJ 3879: SvT [虚树 后缀树]

    传送门 题意: 多次询问,给出一些后缀,求两两之间$LCP$之和 哈哈哈哈哈哈哈竟然$1A$了,刚才还在想如果写不好这道题下节数学就不上了,看来是上天让我上数学课啊 $Suffix\ Virtual\ ...

  9. UOJ.87.mx的仙人掌(圆方树 虚树)(未AC)

    题目链接 本代码10分(感觉速度还行..). 建圆方树,预处理一些东西.对询问建虚树. 对于虚树上的圆点直接做:对于方点特判,枚举其所有儿子,如果子节点不在该方点代表的环中,跳到那个点并更新其val, ...

  10. 洛谷 P6199 - [EER1]河童重工(点分治+虚树)

    洛谷题面传送门 神仙题. 首先看到这样两棵树的题目,我们肯定会往动态树分治的方向考虑.考虑每次找出 \(T_2\) 的重心进行点分治.然后考虑跨过分治中心的点对之间的连边情况.由于连边边权与两棵树都有 ...

随机推荐

  1. java版gRPC实战之七:基于eureka的注册发现

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  2. Set代码

    现有一整数集(允许有重复元素),初始为空.我们定义如下操作:add x 把 x 加入集合del x 把集合中所有与 x 相等的元素删除ask x 对集合中元素x的情况询问 对每种操作,我们要求进行如下 ...

  3. PHP中的MySQLi扩展学习(四)mysqli的事务与预处理语句

    对于 MySQLi 来说,事务和预处理语句当然是它之所以能够淘汰 MySQL(原始) 扩展的资本.我们之前也已经学习过了 PDO 中关于事务和预处理语句相关的内容.所以在这里,我们就不再多讲理论方面的 ...

  4. jmeter5.2版本 配置元件之参数化详解

    1.方式1 :CSV Data Set Config : 打开方式:配置元件---csv data set config 作用:用于读取txt.csv文件数据,注意:默认txt.csv文件的第一行内容 ...

  5. php无限分类 构建树形结构

    <?php class Classification { const PARENT_ID = 'parentid'; const ID = 'id'; const CHILDREN = 'chi ...

  6. Android Kotlin协程入门

    Android官方推荐使用协程来处理异步问题.以下是协程的特点: 轻量:单个线程上可运行多个协程.协程支持挂起,不会使正在运行协程的线程阻塞.挂起比阻塞节省内存,且支持多个并行操作. 内存泄漏更少:使 ...

  7. 更准确的测试Java程序性能——JMH基准测试

    什么是JMH ​ JMH,即Java Microbenchmark Harness,Java平台下的一套微基准测试工具.如果我们需要测试API性能的话,就可以用上这个工具,所以它并不是取代单元测试的. ...

  8. logback日志入门超级详细讲解

    基本信息 日志:就是能够准确无误地把系统在运行状态中所发生的情况描述出来(连接超时.用户操作.异常抛出等等): 日志框架:就是集成能够将日志信息统一规范后输出的工具包. Logback优势 Logba ...

  9. openlayer 4326与3857坐标互转之Java版

    public class Transform { private static final double PI = Math.PI; private static final double merca ...

  10. Pytorch学习2020春-1-线性回归

    线性回归 主要内容包括: 线性回归的基本要素 线性回归模型从零开始的实现 线性回归模型使用pytorch的简洁实现 线性回归的基本要素 模型 为了简单起见,这里我们假设价格只取决于房屋状况的两个因素, ...