虚树是什么?

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

由于询问数可以非常多,每次无法遍历整棵树。 我们可以用一种叫做虚树(virtual tree)的魔法来解决这一问题。

一般来说,虚树有以下一些性质: 1、虚树的大小与点集同阶 2、如果u,v两点在虚树中,则她们的LCA也在虚树中。

于是,只要把握了虚树,就把握了这个点集的基本形态。

##虚树的构造 其实很朴素

普通的构造 1、将点集按DFS序排序。 2、排序后,求相邻两点的LCA。 3、再排序一次,并去重。 这个时候,虚树上该有的点都在集合中上了。 4、建树,用一个栈来模拟。 //既然已经有了DFS序,我觉得你应该也会建树了。

int dfn[M],lim[M],stk[M],vfa[M];
//dfn:dfs序,lim:子树的dfs序的最大值,vfa:虚树上的父节点
bool cmp(int a,int b){return dfn[a]<dfn[b];}
void vbuild(int array[],int len){//对array数组构建虚树,注意array须两倍len长int top=0,vn;sort(array,array+len,cmp); //使结点按照dfs序顺序有序 for(int i=1;i<len;++i)array[++vn]=Lca(array[i-1],array[i]); //处理出相邻结点的Lca sort(array,array+vn,cmp); //按dfs序再有序 stk[top++]=array[0];for(int i=1;i<vn;++i){if(array[i-1]==array[i])continue;while(lim[stk[top-1]]<dfn[u]) --stop; //不是祖先结点vfa[array[i]]=stk[stop-1];stk[stop++]=array[i];}
}

线性构造法 其实吧,排序可以用基数排序,LCA也可以O(1)求,这样就线性了。

也可以用简单一点的方法,有兴趣的同学可以去学习一下。

##例题 好像,虚树题目都挺朴素的,不会复杂到哪里去。 要出难题的话,要么在点集的产生方式上做文章~~,要么上仙人掌~~。 ###机房网络 从这里讲起吧。 看到互质,先来一波莫比乌斯反演压压惊。 定义f(k)是权值被k整除的点集的中的点两两之间的距离之和。 定义F(k)是最大公约数为k的点集的中的点两两之间的距离之和。 显然,有

根据莫比乌斯反演定理,可以得到:

所求即为F(1)

于是,可以先筛出莫比乌斯函数,再求f(n) 考虑怎么求f(n) 直接枚举权值为n的倍数的点再建虚树就可以了。 问题转化为,求树上任意两点的距离值和。

最后一个问题,任意两点的距离和怎么求? 考虑每条边的贡献 如果有一条从u连向u的父节点的边,那么经过这条边的路径数就是

我觉得已经够清楚的了。

贴代码:


long long calc(int k){int tot=0,n=0,top=0;for(int i=k;i<M;i+=k) FOR(j,0,E[i].size()) arr[n++]=E[i][j];sort(arr,arr+n,cmp);FOR(i,1,n) arr[n++]=LCA(arr[i-1],arr[i]);sort(arr,arr+n,cmp);n=unique(arr,arr+n)-arr;FOR(i,0,n){int u=arr[i];while(top and lim[stk[top-1]]<=dfn[u]) --top;if(top) vFa[u]=stk[top-1];stk[top++]=u;}//构造虚树long long res=0;FOR(i,0,n){Sz[arr[i]]=(A[arr[i]]%k==0);//只有权值被k整除的点才能产生贡献tot+=Sz[arr[i]];}DOR(i,n,1){int u=arr[i],v=vFa[u];res+=(long long)(dis[u]-dis[v])*Sz[u]*(tot-Sz[u]);//考虑每条边对答案的贡献Sz[v]+=Sz[u];}return res;
}
long long work(){long long ans=0;FOR(i,mu[1]=1,M) if(mu[i]){for(int j=i+i;j<M;j+=i) mu[j]-=mu[i];ans+=calc(i)*mu[i];}return ans;
}

复杂度分析:一个点被计入虚树的次数是其因子中的平方自由数的个数。 在100000以内,一个数最多只有64个平方自由数因子, 因此复杂度是O(64n log n)或者O(64n)

###[Bzoj3611][Heoi2014]大工程 这个做虚树入门题挺不错的。 直接造虚树,树形DP该怎么写就怎么写。

###开店 其实这是一个点分治裸题 不过Komachi还是用虚树写出来了,你可以问问她哦。 主要的思路是用线段树维护每个区间的虚树。 然后就可以在O(log n)个虚树上查询了。 查询的时候,可以对dfs序进行分类讨论。 代码总共只有6K,还是比较精炼的。

#ova:虚仙人掌 没什么不一样的。 把圆方树的虚树造出来就可以了。 只是有一点细节,要把方点直接连接的子节点都加入到虚树中的。 虚树大小还是与给出的点集同阶的。

##火车司机出秦川

先做一个圆方树的剖分。 用BIT维护跟到每个点的最长路径,最短路径,全部路径的权值和。 另外用BIT维护每个环的长度。 当LCA是一个环时,路径对环的贡献是环上的一段区间。 对每个环单独求贡献。 这个用括号序列,差分,BIT就可以解决。 时间复杂度是O(n log n + sum k_i log n)

//我写过的最长代码最长的题

#include<bits/stdc++.h>
using namespace std;#define FOR(a,b,c) for(int a=(b),a##_end__=(c);a<a##_end__;++a)
#define DOR(a,b,c) for(int a=(b)-1,a##_end__=(c);a>=a##_end__;--a)
template<class T>inline bool chkmin(T&a,T const&b){return a>b?a=b,true:false;}
template<class T>inline bool chkmax(T&a,T const&b){return a<b?a=b,true:false;}
const int M=600005;
long long Pool[M<<2],*allc=Pool;
struct BIT{long long *bit;int sz;void New(int x){bit=allc,allc+=sz=x;}void add(int i,int x){while(i<sz) bit[i]+=x,i+=i&-i;}long long sum(int i){long long res=0;while(i) res+=bit[i],i^=i&-i;return res;}void update(int l,int r,int x){add(l,x),add(r+1,-x);}
}f,g,h,ring[M];
vector<int>E[M],W[M],Q[M];
int low[M],dfn[M],Fa[M],Len[M],Rank[M],dep[M];
int Son[M],Sz[M],Top[M],id[M],lim[M],Node[M];
int Head[M],Next[M],to[M],vsz[M],sum[M][2];
int X[M],Y[M],type[M],U[M],V[M],C[M],px[M],py[M];
int n,m,q,dfn_cnt,id_cnt,bcc_cnt;void Tarjan(int u,int fa){dfn[u]=low[u]=++dfn_cnt;for(auto v:E[u]) if(v!=fa){if(!dfn[v]){dep[v]=dep[u]+1;Tarjan(v,Fa[v]=u);chkmin(low[u],low[v]);if(low[v]>dfn[u]) W[u].push_back(v);}else chkmin(low[u],dfn[v]);}for(auto v:E[u]) if(dfn[v]>dfn[u] && Fa[v]!=u){int w=++bcc_cnt;Len[w]=dep[v]-dep[u]+1;for(int z=v;z!=u;z=Fa[z]){Rank[z]=dep[z]-dep[u];W[w].push_back(z);}reverse(W[w].begin(),W[w].end());ring[w].New(Len[w]+1);W[u].push_back(w);}
}
void Get_son(int u){Sz[u]=1;dfn[u]=++dfn_cnt;for(auto v:W[u]){dep[v]=dep[Fa[v]=u]+1;Get_son(v);Sz[u]+=Sz[v];if(Sz[v]>Sz[Son[u]]) Son[u]=v;}lim[u]=dfn_cnt;
}
void Get_top(int u,int p){Top[u]=p;Node[id[u]=++id_cnt]=u;if(Son[u]) Get_top(Son[u],p);for(auto v:W[u]) if(!Top[v]) Get_top(v,v);
}
int LCA(int u,int v){while(Top[u]!=Top[v])id[u]<id[v]?(v=Fa[Top[v]]):(u=Fa[Top[u]]);return id[u]<id[v]?u:v;
}
int UP(int u,int d){while(dep[Top[u]]>d) u=Fa[Top[u]];return Node[id[u]+d-dep[u]];
}
void update(int u,int v,int x){if(dep[u]<dep[v]) swap(u,v);if(dep[u]-dep[v]==1){f.update(dfn[u],lim[u],x);g.update(dfn[u],lim[u],x);h.update(dfn[u],lim[u],x);}else if(dep[u]==dep[v]){int w=Fa[u],l=W[w][Len[w]>>1];if(Rank[u]>Rank[v]) swap(u,v);ring[w].add(Rank[v],x);f.update(dfn[w]+1,lim[w],x);if(Rank[u]<<1<Len[w]){h.update(dfn[w]+1,lim[u],x);g.update(dfn[v],dfn[l]-1,x);h.update(dfn[l],lim[w],x);}else{h.update(dfn[w]+1,dfn[l]-1,x);g.update(dfn[l],lim[u],x);h.update(dfn[v],lim[w],x);}}else{int w=Fa[u],l=W[w][Len[w]>>1];bool flag=Rank[u]==1;f.update(dfn[w]+1,lim[w],x);(flag?g:h).update(dfn[w]+1,dfn[l]-1,x);(flag?h:g).update(dfn[l],lim[w],x);ring[w].add(flag?1:Len[w],x);}
}
inline bool cmp(int i,int j){return dfn[i]<dfn[j];}
void solve_circle(int u,long long &res){#define DIS(u,v,f) ((f).sum(dfn[v])-(f).sum(dfn[u]))for(int i=Head[u];i;i=Next[i]){int v=to[i];FOR(k,0,2) sum[u][k]+=sum[v][k];if(sum[v][0] && sum[v][1]) res+=DIS(u,v,f);else if(sum[v][0]) res+=DIS(u,v,g);else if(sum[v][1]) res+=DIS(u,v,h);}
}
void solve_square(int u,long long &res){static int S[M],p[M];int n=vsz[u];FOR(i,0,n+1) S[i]=0;for(int i=Head[u],m=0;i;i=Next[i]){int v=to[i],r=Rank[v];FOR(k,0,2) sum[u][k]+=sum[v][k];if(sum[v][0] && sum[v][1]) ++S[0];else if(sum[v][0]) (r<<1>Len[u])?(++S[n-m]):(++S[0],--S[n-m]);else if(sum[v][1]) (r<<1<Len[u])?(++S[n-m]):(++S[0],--S[n-m]);p[++m]=r;}reverse(p+1,p+n+1);p[n+1]=Len[u];for(auto i:Q[u]){int x=px[i],y=py[i];if(Rank[x]>Rank[y]) swap(x,y);int l=lower_bound(p,p+n,Rank[x])-p;int r=lower_bound(p,p+n,Rank[y])-p;if(type[i]!=(Rank[y]-Rank[x])<<1<Len[u]) ++S[l],--S[r];else ++S[0],++S[r],--S[l];}Q[u].clear();FOR(i,0,n+1){S[i+1]+=S[i];if(S[i]) res+=ring[u].sum(p[i+1])-ring[u].sum(p[i]);}
}
long long query(int k){static int A[M<<1],stk[M];int vn=0,top=0,tot=0,m;FOR(i,0,k){int u=X[i],v=Y[i],w=LCA(u,v);A[vn++]=u,A[vn++]=v;if(w>n){px[i]=UP(u,dep[w]+(w>n));py[i]=UP(v,dep[w]+(w>n));A[vn++]=px[i],A[vn++]=py[i];Q[w].push_back(i);}else px[i]=py[i]=w;++sum[u][type[i]],--sum[px[i]][type[i]];++sum[v][type[i]],--sum[py[i]][type[i]];}sort(A,A+vn,cmp);vn=unique(A,A+vn)-A;FOR(i,1,vn) A[vn++]=LCA(A[i-1],A[i]);sort(A,A+vn,cmp);vn=unique(A,A+vn)-A;stk[top++]=A[0];FOR(i,1,m=vn){#define Link(u,v) (Next[++tot]=Head[u],Head[u]=tot,to[tot]=v)int u=A[i],v,w;while(dfn[u]>lim[stk[top-1]]) --top;v=stk[top-1];if(v>n && dep[u]>dep[v]+1){w=UP(u,dep[v]+1);++vsz[w],++vsz[v];Link(v,w),Link(w,u);stk[top++]=A[vn++]=w;}else ++vsz[v],Link(v,u);stk[top++]=u;}inplace_merge(A,A+m,A+vn,cmp);vn=unique(A,A+vn)-A;long long res=0;DOR(i,vn,0){if(A[i]<=n) solve_circle(A[i],res);else solve_square(A[i],res);}DOR(i,vn,0) Head[A[i]]=vsz[A[i]]=sum[A[i]][0]=sum[A[i]][1]=0;return res;
}int main(){scanf("%d %d %d",&n,&m,&q);FOR(i,1,m+1){int u,v,c;scanf("%d %d %d",&u,&v,&c);E[u].push_back(v);E[v].push_back(u);U[i]=u,V[i]=v,C[i]=c;}bcc_cnt=n;Tarjan(1,-1);dfn_cnt=0;Get_son(1);Get_top(1,1);f.New(bcc_cnt+5);g.New(bcc_cnt+5);h.New(bcc_cnt+5);FOR(i,1,m+1) update(U[i],V[i],C[i]);while(q--){int k,w,x;scanf("%d",&k);FOR(i,0,k) scanf("%d %d %d",&X[i],&Y[i],&type[i]);printf("%lld
",k?query(k):0);scanf("%d %d",&w,&x);if(w) update(U[w],V[w],x-C[w]),C[w]=x;}return 0;
}

#总结 虚树还是比较朴素的技巧。 关键是要想到用虚树解。 造出虚树就只有一个树型DP了。 重点是要找到这个模型。

浅谈虚树(虚仙人掌)相关推荐

  1. 【算法微解读】浅谈线段树

    浅谈线段树 (来自TRTTG大佬的供图) 线段树个人理解和运用时,认为这个是一个比较实用的优化算法. 这个东西和区间树有点相似,是一棵二叉搜索树,也就是查找节点和节点所带值的一种算法. 使用线段树可以 ...

  2. 从MySQL Bug#67718浅谈B+树索引的分裂优化

    从MySQL Bug#67718浅谈B+树索引的分裂优化 1月 6th, 2013 发表评论 | Trackback 问题背景 今天,看到Twitter的DBA团队发布了其最新的MySQL分支:Cha ...

  3. 浅谈oracle树状结构层级查询

    oracle树状结构查询即层次递归查询,是sql语句经常用到的,在实际开发中组织结构实现及其层次化实现功能也是经常遇到的,虽然我是一个java程序开发者,我一直觉得只要精通数据库那么对于java开发你 ...

  4. 浅谈oracle树状结构层级查询测试数据

    浅谈oracle树状结构层级查询 oracle树状结构查询即层次递归查询,是sql语句经常用到的,在实际开发中组织结构实现及其层次化实现功能也是经常遇到的,虽然我是一个java程序开发者,我一直觉得只 ...

  5. 【转】Senior Data Structure · 浅谈线段树(Segment Tree)

    本文章转自洛谷 原作者: _皎月半洒花 一.简介线段树 ps: _此处以询问区间和为例.实际上线段树可以处理很多符合结合律的操作.(比如说加法,a[1]+a[2]+a[3]+a[4]=(a[1]+a[ ...

  6. 浅谈 trie树 及其实现

    定义:又称字典树,单词查找树或者前缀树,是一种用于快速检索的多叉树结构, 如英文字母的字典树是一个26叉树,数字的字典树是一个10叉树. 核心思想:是空间换时间.利用字符串的公共前缀来降低查询时间的开 ...

  7. mysql索引如何分裂节点_从MySQL Bug#67718浅谈B+树索引的分裂优化(转)

    原文链接:http://hedengcheng.com/?p=525 问题背景 今天,看到Twitter的DBA团队发布了其最新的MySQL分支:Changes in Twitter MySQL 5. ...

  8. 构造avl树_浅谈AVL树,B-树,B+树

    B+树索引是B+树在数据库中的一种实现,是最常见也是数据库中使用最为频繁的一种索引.B+树中的B代表平衡(balance),而不是二叉(binary),因为B+树是从最早的平衡二叉树演化而来的.在讲B ...

  9. 浅谈线段树(Segment Tree)

    线段树的概念与性质 线段树首先是一棵树,而且是二叉树.树上的每个节点对应于一个区间[a,b],a,b通常为整数.同一层的节点所代表的区间,互相不重叠.并且同一层的区间加起来是连续的区间,叶子节点的区间 ...

  10. P2495 [SDOI2011]消耗战(树形dp+虚树)

    P2495 [SDOI2011]消耗战 树形dp 状态表示:fuf_ufu​表示以uuu为根的子树中,uuu节点与子树中的关键的"隔开"所需要的最小代价 状态转移: 考虑uuu的一 ...

最新文章

  1. iOS动画开发----打分 数字滚动刷新动画
  2. MSSQL差异备份取系统权限
  3. /etc/rc.local 与 /etc/init.d Linux 开机自动运行程序
  4. c语言中go的作用,go语言与c语言的相互调用
  5. liferay requestrequest和actionRequest用法
  6. c# 无损高质量压缩图片代码
  7. 模拟京东按s键选中输入框
  8. 流处理框架Storm简介
  9. 程序员谨防加班猝死之十大建议(转)
  10. VMware Workstation 安装ssh服务器
  11. HP惠普服务器驱动下载地址
  12. FAT文件系统几点释疑
  13. 谢逸计算机网络,第一届中国计算机实践教育学术会议在南京成功举办
  14. 彼岸花开开彼岸 奈何桥前可奈何 作者:天涯游子
  15. struggle in the ACM(一)
  16. 如何设置通过PMU的gpio来唤醒系统
  17. win10防火墙打不开,设置是灰色的、edge闪退、应用商店灰色等问题
  18. Python While循环与break语句_加工零件
  19. Cadedce Allegro 里面怎么切线删除一条线上的某一段
  20. Linux命令之投影密码开启和关闭命令

热门文章

  1. 如何快速在Mac 安装 jq 命令行 JSON 解析器
  2. 家用带宽二级路由openwrt设置ipv6
  3. NET开发邮件发送功能的全面教程(含邮件组件源码)(
  4. JDK--box和unbox
  5. 《Learning to Reconstruct Botanical Trees from Single Images》学习从单幅图像重建植物树
  6. html扇形展开,css如何画扇形?
  7. 无法连接虚拟设备 sata0:1,因为主机上没有相对应的设备——解决方案
  8. Python中的程序控制结构 顺序结构和选择结构
  9. 从源码看ANDROID中SQLITE是怎么通过CURSORWINDOW读DB的
  10. Python基础语法学习6