图论杂项细节梳理&模板(虚树,圆方树,仙人掌,还有。。。)
Posted flashhu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了图论杂项细节梳理&模板(虚树,圆方树,仙人掌,还有。。。)相关的知识,希望对你有一定的参考价值。
虚树
%自为风月马前卒巨佬%
用于优化一类树形DP问题。
当状态转移只和树中的某些关键点有关的时候,我们把这些点和它们两两之间的LCA弄出来,以点的祖孙关系连成一棵新的树,这就是虚树。
容易证明,如果关键点数量为(m),则虚树点数不超过(2m)。
虚树的构建
dfs原树,对点进行dfn标号,并将关键点按dfn从小到大排序。
搞个栈,栈内的点满足:都在从栈顶的点到原树的根的一条链上。
现在我们准备加入一个点(x)
直接加可能破坏一条链的性质,于是把栈顶的元素弹掉直到可以加入为止。求个LCA讨论一波,具体参考代码。
弹栈的时候就可以连好虚树边了。
int p=0;//st[0]代表一个dfn为0的0号空点,方便处理
sort(a+1,a+m+1,cmp);//按dfn排序
for(int i=1;i<=m;st[++p]=a[i++]){
int y=lca(a[i],st[p]);
while(p&&dfn[st[p-1]]>=dfn[y])
add(st[p-1],st[p]),--p;
if(y!=st[p])add(y,st[p]),st[p]=y;//注意判断
}
while(p>1)add(st[p-1],st[p]),--p;//st[1]应为虚树根
当然,可能有些题的虚树在关键点之间也有限制?写出来都不一样。
比如洛谷P2495 [SDOI2011]消耗战
有一个固定的(1)号点,再就是只能保留没有祖孙关系((1)号点除外)的关键点。写法也有好几处不一样
int p=0;st[0]=1;
sort(h+1,h+k+1,cmp);
for(R i=1;i<=k;++i){
if(!p){st[++p]=h[i];continue;}
R x=h[i],y=lca(x,st[p]);
if(y==st[p])continue;
while(p&&l[st[p-1]]>=l[y])add(st[p-1],st[p]),--p;
if(y!=st[p])add(y,st[p]),st[p]=y;
st[++p]=x;
}
while(p)add(st[p-1],st[p]),--p;
所以看来虚树这个东西关键不在于背板子,而在于灵活运用。
题目
洛谷P3233 [HNOI2014]世界树
每个询问建虚树,两遍dfs确定每个虚树上的点被哪里管理(第一遍从下往上更新,第二遍从上往下)
对于两个虚树点中间的部分,倍增找出临界点,两边的size分开贡献。
找临界点是个极其恶心的讨论就对了。
倍增代码短常数大,表示基本没有看到别的小于2.5k的代码。。。
#include<bits/stdc++.h>
#define R register int
#define G if(++ip==ie)if(fread(ip=buf,1,SZ,stdin))
using namespace std;
const int SZ=1<<19,N=3e5+9,M=2*N;
char buf[SZ],*ie=buf+SZ,*ip=ie-1;
inline int in(){
G;while(*ip<'-')G;
R x=*ip&15;G;
while(*ip>'-'){x*=10;x+=*ip&15;G;}
return x;
}
int p,he[N],ne[M],to[M],l[N],sr[N],d[N],o[N],fa[N][20];
void dfs(R x,R f){
l[x]=++p;sr[x]=1;d[x]=d[f]+1;fa[x][0]=f;
for(R&i=o[x];(fa[x][i+1]=fa[fa[x][i]][i]);++i);
for(R i=he[x];i;i=ne[i])
if(to[i]!=f)dfs(to[i],x),sr[x]+=sr[to[i]];
}
int lca(R x,R y){
if(d[x]<d[y])swap(x,y);
for(R i=o[x];~i;--i)
if(d[fa[x][i]]>=d[y])x=fa[x][i];
if(x==y)return x;
for(R i=o[x];~i;--i)
if(fa[x][i]!=fa[y][i])x=fa[x][i],y=fa[y][i];
return fa[x][0];
}
namespace VT{
int h[N],a[N],st[N],he[N],ne[N],tp[N],mn[N],id[N],si[N],ans[N],ok[N];
inline bool cmp(R x,R y){
return l[x]<l[y];
}
inline void add(R x,R y){
ne[y]=he[x];he[x]=tp[y]=y;
for(R i=0,k=d[y]-d[x]-1;k;k>>=1,++i)
if(k&1)tp[y]=fa[tp[y]][i];
}
inline void chkmn(R x,R y){
R t=mn[y]+abs(d[y]-d[x]);
if(mn[x]>t)mn[x]=t,id[x]=id[y];
else if(mn[x]==t&&h[id[x]]>h[id[y]])id[x]=id[y];
}
void calc(R x,R y){
R z=y,p=d[x]-mn[x]+d[y]+mn[y];
if(p&1)p=(p+1)>>1;
else p=(p>>1)+(h[id[x]]<h[id[y]]||mn[x]+d[x]==mn[y]+d[y]);
for(R i=0,k=d[y]-p;k;k>>=1,++i)
if(k&1)z=fa[z][i];
ans[id[y]]+=sr[z]-si[y];
ans[id[x]]+=sr[tp[y]]-sr[z];
he[y]=si[y]=0;
}
void dfsup(R x){
if(!ok[x])mn[x]=M;
for(R y=he[x];y;y=ne[y])
dfsup(y),chkmn(x,y),si[x]+=sr[tp[y]];
}
void dfsdn(R x){
for(R y=he[x];y;y=ne[y])
chkmn(y,x),dfsdn(y),calc(x,y);
}
void work(){
R m=in(),p=0;
for(R i=1;i<=m;++i){
R x=h[i]=a[i]=in();
mn[x]=0,id[x]=i,ok[x]=1;
}
sort(a+1,a+m+1,cmp);
for(R i=1;i<=m;st[++p]=a[i++]){
R y=lca(a[i],st[p]);
while(p&&l[st[p-1]]>=l[y])add(st[p-1],st[p]),--p;
if(y!=st[p])add(y,st[p]),st[p]=y;
}
while(p)add(st[p-1],st[p]),--p;
dfsup(0);dfsdn(0);he[0]=0;
for(R i=1;i<=m;++i)printf("%d ",ans[i]),ok[h[i]]=ans[i]=0;puts("");
}
}
int main(){
R n=in();to[he[0]=1]=1;
for(R i=1,p=1;i<n;++i){
R x=in(),y=in();
ne[++p]=he[x];to[he[x]=p]=y;
ne[++p]=he[y];to[he[y]=p]=x;
}
dfs(0,0);
for(R q=in();q;--q)VT::work();
return 0;
}
仙人掌
圆方树
更新中
以上是关于图论杂项细节梳理&模板(虚树,圆方树,仙人掌,还有。。。)的主要内容,如果未能解决你的问题,请参考以下文章