虚树 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\) 的重心进行点分治.然后考虑跨过分治中心的点对之间的连边情况.由于连边边权与两棵树都有 ...
随机推荐
- go中语句为什么不用加分号;结束
不用人加 编译的时候自动加了分号; 编译器工作原理 首先,在一行中,寻找成对的符号,比如一对字符串的引号.一对圆括号,一对大括号 上述任务完成后,在一行中没有其他成对的标示,然后就在行尾追加分号; 所 ...
- html 表单input disabled属性提交后台无法获得数据
在input上加入disabled属性后, 点击提交会遗漏该值, 有两个办法: 一 可以考虑readonly属性,一样的不可修改操作,但是可以提交 二 在提交时 js 代码操作去除input上的dis ...
- express 路由匹配和数据获取
express配置路由只需要通过app.method(url,func)来配置,其中url配置和其中的参数获取方法不同 直接写全路径 路由中允许存在. get请求传入的参数 router.get(&q ...
- vue 学习资料
自学资料地址: https://zhuanlan.zhihu.com/p/26535530项目UI部分1.pc站 UI:(1)考虑自己写成本高,需要花费不少时间,好处是可以自己控制维护!(2)引入第三 ...
- markdown写作系统
markdown 电脑本地使用typora,可保存为md文件直接上传到有道云笔记中 直接使用博客园做为图床,可以存为草稿,然后把复制连接 有道云笔记 粘贴博客园的图片连接
- Java学习之随堂笔记系列——day02
昨天内容回顾1.安装jdk和配置环境变量 配置JAVA_HOME和path,只要配置成功之后就可以直接使用java和javac命令.2.HelloWorld案例3.java的基础语法 注释:给程序的解 ...
- HTML在网页上不能显示图片问题
我遇到的问题是写了一个HTML程序,结果在网页上面不能显示,原因是图片路径放置错了. 修改前代码: <!DOCTYPE html> <html> <head> &l ...
- 不关闭selinux下配置php+httpd访问KingbaseES
在不关闭selinux的情况下使httpd+php+KingbaseES正常使用1.正常设置php.apache 除了正常流程外还需要在/etc/sysconfig/httpd最后追加LD_LIBRA ...
- springweb项目自定义拦截器修改请求报文头
面向切面,法力无边,任何脏活累活,都可以从干干净净整齐划一的业务代码中抽出来,无非就是加一层,项目里两个步骤间可以被分层的设计渗透成筛子. 举个例子: 最近我们对接某银行接口,我们的web服务都是标准 ...
- .Net Core利用反射动态加载类库的方法(解决类库不包含Nuget依赖包的问题)
在.Net Framework时代,生成类库只需将类库项目编译好,然后拷贝到其他项目,即可引用或动态加载,相对来说,比较简单.但到了.Net Core时代,动态加载第三方类库,则稍微麻烦一些. 一.类 ...