题目传送门

解题思路

正解当然是虚树了。

首先对于原树以及虚树各开一个结构体存边,这个不用多说。

然后我们先 DFS 一遍,求出各个节点的时间戳,子树大小,深度以及父亲节点,并初始化倍增 LCA 。

对于每一次的操作,我们都建一棵虚树(注意数组的清空),为了方便,我们此后操作的 DFS 都从 1 节点开始,如果 1 节点不是临时议事处,我们也把它给加入到虚树中并做一下标记。

显然,在建虚树之前,要先把所有的临时议事处按照时间戳排序,然后建虚树。

此后没,我们进行两遍 DFS :

  • 第一遍:用于更新虚树中所有节点的 dp 以及其所属的临时议事处。

    因为虚树的深度较小的点不一定是临时议事处,而所有的叶子节点一定是临时议事处。

    所以,先遍历到叶子节点,再用叶子节点来更新父亲节点。

  • 第二遍:利用所有的虚树中的值进而求出原树中的值。

    1. 如果子树中没有临时议事处,那么这个节点管辖的点数就要加上子树的siz。

    2. 在原树上是一条链,并且两端都是临时议事处,那么直接一半一半分就好了。

    • 注意:此过程也需要更新虚树上点的 dp 值以及所属临时议事处,但是不同与第一次 DFS 的是由父亲节点更新叶子节点。

最后输出时注意判断 1 节点是否是临时议事处就好了。

code

#include<bits/stdc++.h>
using namespace std;
const int N=3e5+10,M=N<<1,INF=1e9;
int n,m,Q,tim,s[N],dp[N],g[N],dfn[N],q[N],ans[N],f[N][25],siz[N],dep[N];
int top,sta[N];
bool flag,vis[N];
struct Edge
{
int tot,head[N],nxt[M],ver[M];
void add(int x,int y)
{
ver[++tot]=y;
nxt[tot]=head[x];
head[x]=tot;
}
}e1,e2;
void dfs(int x,int fat)
{
f[x][0]=fat;
siz[x]=1;
dfn[x]=++tim;
for(int i=e1.head[x];i;i=e1.nxt[i])
{
int to=e1.ver[i];
if(to==fat)
continue;
dep[to]=dep[x]+1;
dfs(to,x);
siz[x]+=siz[to];
}
}
void LCA_init()
{
dep[1]=1;
dfs(1,0);
for(int i=1;i<=20;i++)
for(int j=1;j<=n;j++)
f[j][i]=f[f[j][i-1]][i-1];
}
int LCA_ask(int x,int y)
{
if(x==y)
return x;
if(dep[x]>dep[y])
swap(x,y);
for(int i=20;i>=0;i--)
if(dep[f[y][i]]>=dep[x])
y=f[y][i];
if(x==y)
return x;
for(int i=20;i>=0;i--)
if(f[x][i]!=f[y][i])
{
x=f[x][i];
y=f[y][i];
}
return f[x][0];
}
bool comp(int x,int y)
{
return dfn[x]<dfn[y];
}
void build(int x)
{
if(!top)
{
sta[++top]=x;
return ;
}
int lca=LCA_ask(x,sta[top]);
while(top>1&&dep[lca]<dep[sta[top-1]])
{
e2.add(sta[top-1],sta[top]);
e2.add(sta[top],sta[top-1]);
top--;
}
if(dep[lca]<dep[sta[top]])
{
e2.add(lca,sta[top]);
e2.add(sta[top],lca);
top--;
}
if(!top||sta[top]!=lca)
sta[++top]=lca;
sta[++top]=x;
}
void dfs1(int x,int fa)
{
dp[x]=INF;
for(int i=e2.head[x];i;i=e2.nxt[i])
{
int to=e2.ver[i];
if(to==fa)
continue;
dfs1(to,x);
int dis=dep[to]-dep[x];
if(dp[x]>dp[to]+dis)
{
g[x]=g[to];
dp[x]=dp[to]+dis;
}
else if(dp[x]==dp[to]+dis)
g[x]=min(g[x],g[to]);
}
if(vis[x])
{
dp[x]=0;
g[x]=x;
}
}
void work(int x,int y)
{
int b=y;
for(int i=20;i>=0;i--)
{
int l=dep[y]-dep[f[b][i]]+dp[y],r=dep[f[b][i]]-dep[x]+dp[x];
if(dep[f[b][i]]>dep[x]&&(l<r||(l==r&&g[y]<g[x])))
b=f[b][i];
}
ans[g[y]]+=siz[b]-siz[y];
ans[g[x]]-=siz[b];
}
void dfs2(int x,int fa)
{
for(int i=e2.head[x];i;i=e2.nxt[i])
{
int to=e2.ver[i];
if(to==fa)
continue;
int dis=dep[to]-dep[x];
if(dp[to]>dp[x]+dis)
{
g[to]=g[x];
dp[to]=dp[x]+dis;
}
else if(dp[to]==dp[x]+dis)
g[to]=min(g[to],g[x]);
work(x,to);
dfs2(to,x);
}
ans[g[x]]+=siz[x];
}
int main()
{
scanf("%d",&n);
for(int i=1,x,y;i<n;i++)
{
scanf("%d%d",&x,&y);
e1.add(x,y);
e1.add(y,x);
}
LCA_init();
scanf("%d",&Q);
while(Q--)
{
flag=top=e2.tot=0;
memset(vis,false,sizeof(vis));
memset(e2.head,0,sizeof(e2.head));
scanf("%d",&m);
for(int i=1;i<=m;i++)
{
scanf("%d",&q[i]);
vis[q[i]]=true;
ans[q[i]]=0;
}
if(!vis[1])
{
flag=true;
q[++m]=1;
}
for(int i=1;i<=m;i++)
s[i]=q[i];
sort(s+1,s+m+1,comp);
for(int i=1;i<=m;i++)
build(s[i]);
if(top)
while(--top)
{
e2.add(sta[top],sta[top+1]);
e2.add(sta[top+1],sta[top]);
}
dfs1(1,0);
dfs2(1,0);
for(int i=1;i<=m;i++)
if(q[i]!=1||!flag)
printf("%d ",ans[q[i]]);
printf("\n");
}
return 0;
}

题解 P3233 [HNOI2014]世界树的更多相关文章

  1. ●洛谷P3233 [HNOI2014]世界树

    题链: https://www.luogu.org/problemnew/show/P3233题解: 虚树,dp,倍增. 首先对于每个询问,要把虚树建出来,这一步就从略了.这里着重分享一下如何求答案. ...

  2. 洛谷 P3233 [HNOI2014]世界树(虚树+dp)

    题面 luogu 题解 数据范围已经告诉我们是虚树了,考虑如何在虚树上面\(dp\) 以下摘自hzwer博客: 构建虚树以后两遍dp处理出虚树上每个点最近的议事处 然后枚举虚树上每一条边,考虑其对两端 ...

  3. 洛谷P3233 [HNOI2014]世界树

    虚树= = #include<cstdio> #include<cstdlib> #include<algorithm> #include<cstring&g ...

  4. luogu P3233 [HNOI2014]世界树

    传送门 我是什么时候写的这题的qwq 首先,发现关键点的总数被限制了,很自然想到虚树,并且,对于一个关键点,他管理的点显然是一个联通块 然后把虚树先建出来,然后两次dfs,第一次是向祖先更新离每个点最 ...

  5. 【题解】HNOI2014世界树

    脑子不清醒的时候千万别写题.写题写不下去了千万别死扛,重构才是你唯一的出路QAQ 昨天很想快点写道题,思路没有很清晰的时候就写了,结果……今天一怒之下决定重整思路重构代码,其实不过是半个小时的事情…… ...

  6. P3233 [HNOI2014]世界树

    传送门 看到指定的总节点数小于等于 300000 就知道要搞虚树了 考虑如何在虚树确定每个议事处控制的节点数量 可以两遍dfs 第一遍求儿子对父亲的影响,第二遍求父亲对儿子影响 注意搜索顺序,这样就可 ...

  7. [题解] [HNOI2014] 世界树

    题面 [HNOI2014]世界树 题解 从数据范围很容易看出是个虚树DP(可惜看出来了也还是不会做) 虚树大家应该都会, 不会的话自己去搜吧, 我懒得讲了, 我们在这里只需要考虑如何DP即可 首先我们 ...

  8. [BZOJ3572][Hnoi2014]世界树

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

  9. 【BZOJ3572】[Hnoi2014]世界树 虚树

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

随机推荐

  1. JVM虚拟机 类加载过程与类加载器

    目录 前言 类的生命周期 类加载过程 加载 连接 验证 准备 解析 初始化 类加载器 三大类加载器 双亲委派模型 概念 为什么要使用双亲委派模型 源码分析 反双亲委派模型 参考 前言 类装载器子系统是 ...

  2. 解决Latex输出PDF纸张自适应大小及中文无法显示问题

    遗留的问题 之前我们进行了基于texlive定制chemfig化学式转换Python服务镜像,虽然完成pdf的输出服务改造,但是输出效果并不是太好,如下图: 这个图有两个比较严重问题 不支持中文 空白 ...

  3. OOP第二章博客

    OO第二次博客作业 (1)作业分析 三次作业在处理多线程的协同配合时都是使用将同步放在自己写的"线程安全类"(经测试有些许漏洞_,但是不影响结果就是了): 我个人倾向于把wait( ...

  4. 手写Promise中then方法返回的结果或者规律

    1. Promise中then()方法返回来的结果或者规律 我们知道 promise 的 then 方法返回来的结果值[result]是由: 它指定的回调函数的结果决定的 2.比如说下面这一段代码 l ...

  5. Docker 中运行 ElasticSearch 和 Kibana

    ElasticSearch 是一个基于Lucene的搜索服务器.它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口:Kibana 是一个开源的分析和可视化平台,可以搜索,查看. ...

  6. 登录框-element-ui 样式调节

    element-ui样式调节 首先设置布局 如果想要实现如下效果 需要两行,然后设置偏移,第一行中间只是站位,没有内容,可以考虑使用div占位,设置最小高度 el-card调整圆角 border-ra ...

  7. 二进制部署K8S-3核心插件部署

    二进制部署K8S-3核心插件部署 5.1. CNI网络插件 kubernetes设计了网络模型,但是pod之间通信的具体实现交给了CNI往插件.常用的CNI网络插件有:Flannel .Calico. ...

  8. 11.15 dmidecode:查询系统硬件信息

    dmidecode命令可以用来在Linux系统下获取硬件方面的信息.dmidecode遵循SMBIOS/DMI标准,其输出的信息包括BIOS.处理器.内存.缓存等. dmidecode命令的参数选项及 ...

  9. vue项目使用百度地图API获取经纬度

    一.首先在百度api注册获得ak密钥 二.进行引入 (1).第一种方式: 直接在vue中index.html中用script标签引入. //你的ak密钥需要替换真实的你的ak码 <script ...

  10. Spring5.0源码学习系列之事务管理概述

    Spring5.0源码学习系列之事务管理概述(十一),在学习事务管理的源码之前,需要对事务的基本理论比较熟悉,所以本章节会对事务管理的基本理论进行描述 1.什么是事务? 事务就是一组原子性的SQL操作 ...