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

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

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);}
}

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

例题:

  • P2495 [SDOI2011]消耗战

  • P3233 [HNOI2014]世界树

世界树 $-\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. 【Codeforces613D】Kingdom and its Cities【虚树】【Tree DP】

    http://codeforces.com/problemset/problem/613/D 题意: 给出n个点的树,有q个询问,每次询问给出k个重要的点,问至少删掉多少个非重要的点,使得这个重要的点 ...

  2. 【Codeforces613D】Kingdom and its Cities【虚树】【Tree DP】倍增lca

    http://codeforces.com/problemset/problem/613/D 题意: 给出n个点的树,有q个询问,每次询问给出k个重要的点,问至少删掉多少个非重要的点,使得这个重要的点 ...

  3. 牛客多校1 - Infinite Tree(虚树+换根dp+树状数组)

    题目链接:点击查看 题目大意:给出一个无穷个节点的树,对于每个大于 1 的点 i 来说,可以向点 i / minvid[ i ] 连边,这里的 mindiv[ x ] 表示的是 x 的最小质因数,现在 ...

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

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

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

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

  6. 浅谈虚树(虚仙人掌)

    虚树是什么? 在 OI 比赛中,有这样一类题目:给定一棵树,另有多次询问,每个询问给定一些关键点,需要求这些关键点之间的某些信息.询问数可能很多,但满足所有询问中关键点数量的总和比较小. 由于询问数可 ...

  7. 数据结构专题——虚树

    虚树(virtual tree)的概念 虚树 是将一个树的点集的某一个子集,以及该子集中点的 LCALCALCA 的集合,一起所重构出来的一棵树 虚树的用途 在树型dp中,有时候没必要对整颗树进行dp ...

  8. 牛客 - 王国(虚树+树的直径)

    题目链接:点击查看 题目大意:给出 n 个点组成的一棵树,每个节点都有一个权值,现在规定权值相同的节点之间,简单路径的边数为 x ,求 x * x 的最大值 题目分析:真的很巧,上周刚学的虚树,读完这 ...

  9. 牛客 - 血压游戏(虚树+dp)

    题目链接:点击查看 题目大意:中文题,不难理解 题目分析:这个题目比赛的时候没来得及看,比赛结束后看到有大佬写了一篇长链剖分+线段树的题解就被吓到了(主要是感觉太麻烦了,懒得去补了),读完题后总感觉似 ...

  10. CodeForces - 613D Kingdom and its Cities(虚树+贪心)

    题目链接:点击查看 题目大意:给出一棵 n 个结点组成的树,有多组询问,每组询问给出 k 个点,现在可以删除不同于 k 个节点的 m 个节点,使得这 k 个节点两两不连通,要求最小化 m ,如果不可能 ...

最新文章

  1. 你想学Java?资源都在这里了
  2. android 保存数据到setting中_文章如何保存在数据库中
  3. redis命令-key操作
  4. 怎么把php查询到的值显示到下拉框中_RazorSQL for Mac(数据库工具查询)8.5.3
  5. C语言操作符 进阶 (常见错误及细节)
  6. 多选框中的选中的值和未选中值的获取
  7. mysql几搜索引擎_详细介绍基于MySQL的搜索引擎MySQL-Fullltext
  8. 勤哲cad服务器注册机,勤哲CAD服务器
  9. LintCode 最长公共子串
  10. html下拉控件 拼音检索和中文检索,bootstrap select 下拉框通过拼音搜索汉字
  11. Markdown中设置图片尺寸及添加图注
  12. 中小学生安全专用手机
  13. requests中get请求没有referer得不到数据
  14. windows 网络正常 浏览器却打不开网页解决办法
  15. 关于crawl DZDP的城市商场名称和地址的参考
  16. C++打开文件的方式
  17. 【PaperReading】The permutation testing approach: a review
  18. MP4视频文件过大压缩的技巧是什么?简单步骤讲解
  19. java能盗号吗_CVE-2017-8759漏洞新利用:Java Keylogger盗号木马分析
  20. python pyautogui_【Python 教程】PyAutoGUI 使用介绍

热门文章

  1. Anniversary party(最基础的树形dp)
  2. 免费学习coursera的课程的操作办法
  3. 基于ijkplayer实现低延迟直播播放器
  4. windows11如何去桌面快捷键小箭头的方法
  5. 速记计算机键盘,中文速记电脑编码方法及输入键盘技术
  6. Failed to load response dataNo data found for resource with given identifier
  7. scada系统集成_设计 SCADA 应用程序软件
  8. scada java_SCADA开源项目lite版本
  9. 文件运行出现乱码问题的解决方法
  10. 快速原型模型(Rapid Prototype Model)