【题意】给定n个点的树,m次询问,每次给定ki个特殊点,一个点会被最近的特殊点控制,询问每个特殊点控制多少点。n,m,Σki<=300000。

【算法】虚树+倍增

【题解】★参考:thy_asdf

对询问建立虚树,然后DFS统计虚树上每个点被哪个点控制,记为belong[x]。

统计的方法是从下往上DFS得到x来自x子树的控制点,再从上往下DFS得到x来自非x子树的控制点。

令D(x,y)表示点x和点y之间的路径长度,比较x的不同控制点y的方式是取D(x,y)的最小值(相同时比较编号)。

依此考虑虚树上的每一条边(x,y),记z为同时是x的儿子和y的祖先的点,令f[x]表示点x的答案。

如果belong[x]=belong[y],那么f[belong[x]]+=size[z]-size[y]

否则,从y倍增找到分界点mid,满足mid及以下由y控制,mid以上由x控制。

倍增过程中,记A=D(belong[x],mid),B=D(belong[x],mid),当满足A>B或A=B&&belong[x]>belong[y]时进行倍增。

找到分界点mid后,f[belong[x]]+=size[x]-size[m],f[belong[y]+=size[m]-size[y]。

最后要处理没有出现在虚树路径上的点(包括虚树节点),这些点没有在上述过程中被统计。

记rem[x]表示子树x中未被统计的节点数,初始rem[x]=size[x],特别的rem[a[1]]=n(a[1]是虚树最高节点,n是原树总点数,这样是为了保存a[1]往上延伸的节点)

每次处理完边(x,y),rem[x]-=size[z]。

全部做完后,对虚树的每个点f[belong[x]]+=rem[x]。

#include<cstdio>
#include<cctype>
#include<cstring>
#include<algorithm>
using namespace std;
int read(){
int s=,t=;char c;
while(!isdigit(c=getchar()))if(c=='-')t=-;
do{s=s*+c-'';}while(isdigit(c=getchar()));
return s*t;
}
const int maxn=;
int n,N,size[maxn],in[maxn],ou[maxn],f[maxn],a[maxn*],b[maxn];
int deep[maxn],tot,first[maxn],belong[maxn],st[maxn];
int rem[maxn];
bool v[maxn];
namespace cyc{
int first[maxn],tot,dfsnum=,f[maxn][];
struct edge{int v,from;}e[maxn*];
void insert(int u,int v){tot++;e[tot].v=v;e[tot].from=first[u];first[u]=tot;}
void dfs(int x,int fa){
in[x]=++dfsnum;size[x]=;
for(int j=;(<<j)<=deep[x];j++)f[x][j]=f[f[x][j-]][j-];
for(int i=first[x];i;i=e[i].from)if(e[i].v!=fa){
deep[e[i].v]=deep[x]+;
f[e[i].v][]=x;
dfs(e[i].v,x);
size[x]+=size[e[i].v];
}
ou[x]=dfsnum;
}
int lca(int x,int y){
if(deep[x]<deep[y])swap(x,y);
int d=deep[x]-deep[y];
for(int j=;j<=;j++)if(d&(<<j))x=f[x][j];
if(x==y)return x;
for(int j=;j>=;j--)if((<<j)<=deep[x]&&f[x][j]!=f[y][j]){
x=f[x][j];y=f[y][j];
}
return f[x][];
}
void build(){
n=read();
for(int i=;i<n;i++){
int u=read(),v=read();
insert(u,v);insert(v,u);
}
dfs(,-);
}
}
int D(int x,int y){if(!y)return 0x3f3f3f3f;else return deep[x]+deep[y]-*deep[cyc::lca(x,y)];}
bool cmp(int a,int b){return in[a]<in[b];}
bool check(int x,int y){return in[x]<=in[y]&&ou[y]<=ou[x];}
struct edge{int u,v,from;}e[maxn*];
void insert(int u,int v){tot++;e[tot].u=u;e[tot].v=v;e[tot].from=first[u];first[u]=tot;}
void dfs1(int x){
if(v[x])belong[x]=x;
for(int i=first[x];i;i=e[i].from){
dfs1(e[i].v);
int A=D(x,belong[x]),B=D(x,belong[e[i].v]);
if(B<A||(B==A&&belong[e[i].v]<belong[x]))belong[x]=belong[e[i].v];
}
}
void dfs2(int x){
for(int i=first[x];i;i=e[i].from){
int A=D(e[i].v,belong[x]),B=D(e[i].v,belong[e[i].v]);
if(A<B||(A==B&&belong[x]<belong[e[i].v]))belong[e[i].v]=belong[x];
dfs2(e[i].v);
}
}
void solve(int x,int y){
int z=y;
for(int i=;i>=;i--)if((<<i)<=deep[z]&&deep[cyc::f[z][i]]>=deep[x]+)z=cyc::f[z][i];
rem[x]-=size[z];
if(belong[x]==belong[y]){f[belong[x]]+=size[z]-size[y];return;}//
int m=y;
for(int i=;i>=;i--)if((<<i)<=deep[m]){
if(deep[cyc::f[m][i]]<deep[x])continue;
int A=D(cyc::f[m][i],belong[x]),B=D(cyc::f[m][i],belong[y]);
if(B<A||(A==B&&belong[x]>belong[y]))m=cyc::f[m][i];//
}
f[belong[x]]+=size[z]-size[m];
f[belong[y]]+=size[m]-size[y];
}
void work(){
int last=read();N=last;
for(int i=;i<=N;i++)a[i]=read(),f[b[i]=a[i]]=,v[a[i]]=;
sort(a+,a+N+,cmp);
for(int i=;i<last;i++)a[++N]=cyc::lca(a[i],a[i+]);
sort(a+,a+N+,cmp);
N=unique(a+,a+N+)-a-;
tot=;
for(int i=;i<=N;i++)first[a[i]]=,belong[a[i]]=,rem[a[i]]=size[a[i]];
rem[a[]]=n;
int top=;
for(int i=;i<=N;i++){
while(top&&!check(st[top],a[i]))top--;
if(top)insert(st[top],a[i]);
st[++top]=a[i];
}
dfs1(a[]);dfs2(a[]);
for(int i=;i<=tot;i++)solve(e[i].u,e[i].v);
for(int i=;i<=N;i++)f[belong[a[i]]]+=rem[a[i]];
for(int i=;i<=last;i++)printf("%d ",f[b[i]]),v[b[i]]=;
}
int main(){
cyc::build();
int m=read();
while(m--)work();
return ;
}

(倍增求LCA)

注意,由于多次调用LCA,所以用倍增LCA算法有很大的常数,甚至在LOJ会导致TLE。

但是倍增求LCA会使过程更简洁。

下面这份代码是树链剖分求LCA,代码可读性不高。

#include<cstdio>
#include<cctype>
#include<cstring>
#include<algorithm>
using namespace std;
int read(){
int s=,t=;char c;
while(!isdigit(c=getchar()))if(c=='-')t=-;
do{s=s*+c-'';}while(isdigit(c=getchar()));
return s*t;
}
const int maxn=;
int n,N,size[maxn],in[maxn],ou[maxn],f[maxn],a[maxn*],b[maxn];
int deep[maxn],tot,first[maxn],belong[maxn],st[maxn];
int rem[maxn];
bool v[maxn];
namespace cyc{
int first[maxn],tot,dfsnum=,f[maxn][];
struct edge{int v,from;}e[maxn*];
void insert(int u,int v){tot++;e[tot].v=v;e[tot].from=first[u];first[u]=tot;}
void dfs(int x,int fa){
in[x]=++dfsnum;size[x]=;
for(int j=;(<<j)<=deep[x];j++)f[x][j]=f[f[x][j-]][j-];
for(int i=first[x];i;i=e[i].from)if(e[i].v!=fa){
deep[e[i].v]=deep[x]+;
f[e[i].v][]=x;
dfs(e[i].v,x);
size[x]+=size[e[i].v];
}
ou[x]=dfsnum;
}
int lca(int x,int y){
if(deep[x]<deep[y])swap(x,y);
int d=deep[x]-deep[y];
for(int j=;j<=;j++)if(d&(<<j))x=f[x][j];
if(x==y)return x;
for(int j=;j>=;j--)if((<<j)<=deep[x]&&f[x][j]!=f[y][j]){
x=f[x][j];y=f[y][j];
}
return f[x][];
}
void build(){
n=read();
for(int i=;i<n;i++){
int u=read(),v=read();
insert(u,v);insert(v,u);
}
dfs(,-);
}
}
int D(int x,int y){if(!y)return 0x3f3f3f3f;else return deep[x]+deep[y]-*deep[cyc::lca(x,y)];}
bool cmp(int a,int b){return in[a]<in[b];}
bool check(int x,int y){return in[x]<=in[y]&&ou[y]<=ou[x];}
struct edge{int u,v,from;}e[maxn*];
void insert(int u,int v){tot++;e[tot].u=u;e[tot].v=v;e[tot].from=first[u];first[u]=tot;}
void dfs1(int x){
if(v[x])belong[x]=x;
for(int i=first[x];i;i=e[i].from){
dfs1(e[i].v);
int A=D(x,belong[x]),B=D(x,belong[e[i].v]);
if(B<A||(B==A&&belong[e[i].v]<belong[x]))belong[x]=belong[e[i].v];
}
}
void dfs2(int x){
for(int i=first[x];i;i=e[i].from){
int A=D(e[i].v,belong[x]),B=D(e[i].v,belong[e[i].v]);
if(A<B||(A==B&&belong[x]<belong[e[i].v]))belong[e[i].v]=belong[x];
dfs2(e[i].v);
}
}
void solve(int x,int y){
int z=y;
for(int i=;i>=;i--)if((<<i)<=deep[z]&&deep[cyc::f[z][i]]>=deep[x]+)z=cyc::f[z][i];
rem[x]-=size[z];
if(belong[x]==belong[y]){f[belong[x]]+=size[z]-size[y];return;}//
int m=y;
for(int i=;i>=;i--)if((<<i)<=deep[m]){
if(deep[cyc::f[m][i]]<deep[x])continue;
int A=D(cyc::f[m][i],belong[x]),B=D(cyc::f[m][i],belong[y]);
if(B<A||(A==B&&belong[x]>belong[y]))m=cyc::f[m][i];//
}
f[belong[x]]+=size[z]-size[m];
f[belong[y]]+=size[m]-size[y];
}
void work(){
int last=read();N=last;
for(int i=;i<=N;i++)a[i]=read(),f[b[i]=a[i]]=,v[a[i]]=;
sort(a+,a+N+,cmp);
for(int i=;i<last;i++)a[++N]=cyc::lca(a[i],a[i+]);
sort(a+,a+N+,cmp);
N=unique(a+,a+N+)-a-;
tot=;
for(int i=;i<=N;i++)first[a[i]]=,belong[a[i]]=,rem[a[i]]=size[a[i]];
rem[a[]]=n;
int top=;
for(int i=;i<=N;i++){
while(top&&!check(st[top],a[i]))top--;
if(top)insert(st[top],a[i]);
st[++top]=a[i];
}
dfs1(a[]);dfs2(a[]);
for(int i=;i<=tot;i++)solve(e[i].u,e[i].v);
for(int i=;i<=N;i++)f[belong[a[i]]]+=rem[a[i]];
for(int i=;i<=last;i++)printf("%d ",f[b[i]]),v[b[i]]=;
}
int main(){
cyc::build();
int m=read();
while(m--)work();
return ;
}

upd:复习一下。

【建虚树】

第一步、所有关键点按DFS序排序,加入LCA后排序去重。

第二步、按顺序用栈维护构造出虚树。

第三步、虚树DP后清零。

【此题的虚树DP】

第一步、处理虚树上点与点。

第二步、处理虚树边上点的子树。

第三步、处理虚树点的 [ 不在虚树上的孩子 ] 子树。(通过减虚树孩子的sz得到)

【BZOJ】3572: [Hnoi2014]世界树 虚树+倍增的更多相关文章

  1. bzoj 3572: [Hnoi2014]世界树 虚树 && AC500

    3572: [Hnoi2014]世界树 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 520  Solved: 300[Submit][Status] ...

  2. BZOJ 3572: [Hnoi2014]世界树 [虚树 DP 倍增]

    传送门 题意: 一棵树,多次询问,给出$m$个点,求有几个点到给定点最近 写了一晚上... 当然要建虚树了,但是怎么$DP$啊 大爷题解传送门 我们先求出到虚树上某个点最近的关键点 然后枚举所有的边$ ...

  3. bzoj 3572 [Hnoi2014]世界树——虚树

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=3572 关于虚树:https://www.cnblogs.com/zzqsblog/p/556 ...

  4. BZOJ 3572: [Hnoi2014]世界树 虚树 树形dp

    https://www.lydsy.com/JudgeOnline/problem.php?id=3572 http://hzwer.com/6804.html 写的时候参考了hzwer的代码,不会写 ...

  5. BZOJ 3572 [HNOI2014]世界树 (虚树+DP)

    题面:BZOJ传送门 洛谷传送门 题目大意:略 细节贼多的虚树$DP$ 先考虑只有一次询问的情况 一个节点$x$可能被它子树内的一个到x距离最小的特殊点管辖,还可能被管辖fa[x]的特殊点管辖 跑两次 ...

  6. bzoj 3572: [Hnoi2014]世界树 虚树

    题目: Description 世界树是一棵无比巨大的树,它伸出的枝干构成了整个世界.在这里,生存着各种各样的种族和生灵,他们共同信奉着绝对公正公平的女神艾莉森,在他们的信条里,公平是使世界树能够生生 ...

  7. BZOJ 3572: [Hnoi2014]世界树

    BZOJ 3572: [Hnoi2014]世界树 标签(空格分隔): OI-BZOJ OI-虚数 OI-树形dp OI-倍增 Time Limit: 20 Sec Memory Limit: 512 ...

  8. bzoj 3572 [Hnoi2014]世界树(虚树+DP)

    3572: [Hnoi2014]世界树 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 645  Solved: 362[Submit][Status] ...

  9. bzoj3572[Hnoi2014] 世界树 虚树+dp+倍增

    [Hnoi2014]世界树 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 1921  Solved: 1019[Submit][Status][Dis ...

随机推荐

  1. 第三章 广义线性模型(GLM)

    广义线性模型 前面我们举了回归和分类得到例子.在回归的例子中,$y \mid x;\theta \sim  N(u,\sigma ^{2})$,在分类例子中,$y\mid x;\theta \sim ...

  2. C语言为运算及 两个变量的赋值问题

    #include <stdio.h>#define ARRAY_SIZE 10int main() {    int arr[ARRAY_SIZE] = {51,116,53,120,85 ...

  3. JVM初识、调优

    JVM是按照运行时数据的存储结构来划分内存结构的,JVM在运行java时,将他们划分成几种不同格式的数据,分别存储在不同的区域,这些数据统一称为运行时数据,运行时数据包括java程序本身的数据信息和J ...

  4. 51nod 1799 二分答案(分块打表)

    首先题目等价于求出满足运行二分程序后最后r=k的排列种数. 显然对于这样的二分程序,起作用的只有mid点,mid处的值决定了接下来要递归的子区间. 于是可以一遍二分求出有多少个mid点处的值<= ...

  5. 洛谷P2740 [USACO4.2]草地排水Drainage Ditches

    题目背景 在农夫约翰的农场上,每逢下雨,贝茜最喜欢的三叶草地就积聚了一潭水.这意味着草地被水淹没了,并且小草要继续生长还要花相当长一段时间.因此,农夫约翰修建了一套排水系统来使贝茜的草地免除被大水淹没 ...

  6. KMP算法模板(pascal)

    洛谷P3375: program rrr(input,output); var i,j,lena,lenb:longint; a,b:ansistring; next:..]of longint; b ...

  7. 【bzoj2669】[cqoi2012]局部极小值 容斥原理+状压dp

    题目描述 有一个n行m列的整数矩阵,其中1到nm之间的每个整数恰好出现一次.如果一个格子比所有相邻格子(相邻是指有公共边或公共顶点)都小,我们说这个格子是局部极小值. 给出所有局部极小值的位置,你的任 ...

  8. logstash收集MySQL慢查询日志

    #此处以收集mysql慢查询日志为准,根据文件名不同添加不同的字段值input { file { path => "/data/order-slave-slow.log" t ...

  9. [SDOI2011]黑白棋 kth - nim游戏

    题面 题面 题解 观察题目,我们可以发现,这个游戏其实就是不断再把对方挤到一边去,也就是黑子不断往左走,白子不断往右走. 因此可以发现,如果将黑白子按顺序两两配对,那么它们中间的距离会不断缩小,且每次 ...

  10. 【NOIP考试范围】

    ※号为可能考察的算法[历年有出现过,但概率小,但最好掌握] [本图片仅作参考] 梦想总是要有的,万一实现了呢?