虚树 virtual-tree
我们发现,如果一棵树中真正需要处理的点很少,而总共点数很多时,可以只处理那些需要的点,而忽略其他点。
因此我们可以根据那些需要的点构建虚树,只保留关键点。
我们根据一下方式建立虚树:
现将所有需要处理的关键按照欧拉序排序。
用一个栈维护一条从根节点到上一个处理过个点的链(虚树上的链)。
考虑将一个新的点加入虚树:
求出这个点与栈顶元素的 \(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的更多相关文章
- HDU-6035:Colorful Tree(虚树+DP)
这里有三道长得像的题: 一:HDU6036: There is a tree with nn nodes, each of which has a type of color represented ...
- 【虚树】hdu6161 Big binary tree
题意:一棵n个结点的完全二叉树,初始i号结点的权值为i.有两种操作:单点修改:询问经过某个结点的路径中,权值和最大的路径的权值和是多少. 修改的时候,暴力修改到根节点的路径上的点的f(x)即可. 跟虚 ...
- Codeforces 1111 E. Tree(虚树,DP)
题意 有一棵树,q个询问,每次询问,指定一个点做树根,再给定k个点,要求把这些点分成不超过m组的方案数,分配的限制是任意两个有祖先关系的点不能分在同一组.题目还保证了所有的询问的k加起来不超过1e5. ...
- hdu 6035 Colorful Tree(虚树)
考虑到树上操作:首先题目要我们求每条路径上出现不同颜色的数量,并把所有加起来得到答案:我们知道俩俩点之间会形成一条路径,所以我们可以知道每个样例的总的路径的数目为:n*(n-1)/2: 这样单单的求, ...
- 2020牛客NOIP赛前集训营-提高组(第三场)C-牛半仙的妹子Tree【虚树,最短路】
正题 题目链接:https://ac.nowcoder.com/acm/contest/7609/C 题目大意 给出\(n\)个点的一棵树,\(m\)个时刻各有一个操作 标记一个点,每个点被标记后的每 ...
- Codeforces 1606F - Tree Queries(虚树+树形 dp)
Codeforces 题面传送门 & 洛谷题面传送门 显然我们选择删除的点连同 \(u\) 会形成一个连通块,否则我们如果选择不删除不与 \(u\) 在同一连通块中的点,答案一定更优. 注意到 ...
- 青云的机房组网方案(简单+普通+困难)(虚树+树形DP+容斥)
题目链接 1.对于简单的版本n<=500, ai<=50 直接暴力枚举两个点x,y,dfs求x与y的距离. 2.对于普通难度n<=10000,ai<=500 普通难度解法挺多 ...
- BZOJ 3879: SvT [虚树 后缀树]
传送门 题意: 多次询问,给出一些后缀,求两两之间$LCP$之和 哈哈哈哈哈哈哈竟然$1A$了,刚才还在想如果写不好这道题下节数学就不上了,看来是上天让我上数学课啊 $Suffix\ Virtual\ ...
- UOJ.87.mx的仙人掌(圆方树 虚树)(未AC)
题目链接 本代码10分(感觉速度还行..). 建圆方树,预处理一些东西.对询问建虚树. 对于虚树上的圆点直接做:对于方点特判,枚举其所有儿子,如果子节点不在该方点代表的环中,跳到那个点并更新其val, ...
- 洛谷 P6199 - [EER1]河童重工(点分治+虚树)
洛谷题面传送门 神仙题. 首先看到这样两棵树的题目,我们肯定会往动态树分治的方向考虑.考虑每次找出 \(T_2\) 的重心进行点分治.然后考虑跨过分治中心的点对之间的连边情况.由于连边边权与两棵树都有 ...
随机推荐
- Fastjson 1.2.22-24 反序列化漏洞分析(1)
Fastjson 1.2.22-24 反序列化漏洞分析(1) 前言 FastJson是alibaba的一款开源JSON解析库,可用于将Java对象转换为其JSON表示形式,也可以用于将JSON字符串转 ...
- javassist 使用笔记
javassist Javassist 是一个开源的分析.编辑和创建Java字节码的类库.其主要的优点,在于简单,而且快速.直接使用 java 编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构 ...
- 【第八篇】- Git 查看提交历史之Spring Cloud直播商城 b2b2c电子商务技术总结
Git 查看提交历史 Git 提交历史一般常用两个命令: git log 在使用 Git 提交了若干更新之后,又或者克隆了某个项目,想回顾下提交历史,我们可以使用 git log 命令查看. 针对 ...
- easyx小游戏
#include "stdafx.h" int main(){ srand(time(NULL)); initgraph(640,480); int user_x=20,user_ ...
- 【OI技巧】解决cin、cout因输入输出慢而TLE的问题
语言本身没有问题!! 语言本身没有问题!! 语言本身没有问题!! C++本来的设计就是将输入输出流和C的标准输入输出挂钩在一起,这样就导致cin,cout输入输出前先要将内容放入缓冲区,最后再由cin ...
- STM32,下载HAL库写的代码后J-Link识别不到芯片,必须要按住复位才能下载?
问题描述:最近在学STM32的HAL库,据说可以统一STM32江湖,前途无量.最近一段时间参照STM32CubeMX和原子的资料自己学着建了两个HAL库的工程模板,F4的还好说,F1的出现了一个玄学问 ...
- PHP中的MySQLi扩展学习(一)MySQLi介绍
关于 PDO 的学习我们告一段落,从这篇文章开始,我们继续学习另外一个 MySQL 扩展,也就是除了 PDO 之外的最核心的 MySQLi 扩展.可以说它的祖先,也就是 MySQL(原始) 扩展是我们 ...
- 取得get参数 从url
1. getUrlParam.js define(function() { // url参数 var data, index; (function init() { data = []; index ...
- 监控linux服务器工具nmon的使用
做压测时,需要查看服务器中的cpu.内存变化,但由于服务器是linux环境,则需要监控linux服务器的工具,下面用到的工具是nmon. 1.安装nmon.在网上下载nmon安装包,在linux服务器 ...
- 整理常用的 vim 命令
vim 是一款功能强大的文本编辑器,它是Linux下常用的编辑器之一,对于熟练掌握了 vim 的人来说,用它编辑文件,方便又快捷,能极大的提高工作效率 vim 功能强大,对应的命令也非常的多,对于初学 ...