题目传送门

解题思路

正解当然是虚树了。

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

然后我们先 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. JAVA连接、操作数据库的DBHelper

    工厂模式的DBHelper 1 import java.sql.Connection; 2 import java.sql.DriverManager; 3 import java.sql.Prepa ...

  2. java面试一日一题:如何设计一款垃圾回收器

    问题:如果让你设计一个垃圾回收器,你会考虑哪些问题 分析:该问题主要考察对java中垃圾回收器的理解,要理解怎么回收:一款好的垃圾回收器有哪些衡量指标 回答要点: 主要从以下几点去考虑, 1.垃圾回收 ...

  3. apache common pool2原理与实战

    完整源码,请帮我点个star哦! 原文地址为https://www.cnblogs.com/haixiang/p/14783955.html,转载请注明出处! 简介 对象池顾名思义就是存放对象的池,与 ...

  4. ZOHO荣登“2020中国ToB行业年度企业影响力”榜单

    近日,3WToB行业头条正式揭晓<2020中国ToB行业年度榜单 · 企业影响力榜>. 此次评选,ToB行业头条联合3W集团.50+知名投资机构.60+权威媒体及资深行业人士,进行深度调研 ...

  5. foreign key 多对一 多对对 一对一

    使用foreign key 要清除先有哪张表再有哪张表,后表对应前表 例如现有部门再有员工,所以员工对应部门 现有作者后有书,所以书对应作者 现有潜在顾客后有顾客,所以顾客对应潜在顾客 多对多建立3张 ...

  6. 5.配置IP

    静态IP配置 1.NAT模式设置 首先设置虚拟机中NAT模式的选项,打开VMware,点击"编辑"下的"虚拟网络编辑器",设置NAT参数 注意: VMware ...

  7. VS·调试过程中某个操作导致调试突然退出之解决方案

    阅文时长 | 0.11分钟 字数统计 | 232字符 主要内容 | 1.引言&背景 2.声明与参考资料 『VS·调试过程中某个操作导致调试突然退出之解决方案』 编写人 | SCscHero 编 ...

  8. 小甲鱼零基础入门学习python--课后作业

    [小甲鱼零基础入门学习python--课后作业] 小甲鱼零基础入门学习python--课后作业 本章内容: 1.基础部分的作业 2.函数部分的作业 3.字典.集合.文件部分作业 4.异常 5.Easy ...

  9. 【转载】深入理解OpenStack-手动制作qcow2镜像

    深入理解OpenStack-手动制作qcow2镜像 2018-01-18 手动制作镜像 1 官方虽提供qcow2系统镜像,但对于有需求的企业来说,还是定制镜像比较靠谱,下面就手动定制一个镜像 给虚拟机 ...

  10. Linux 部署 iSCSI 客户端配置(Windows)

    Linux 部署 iSCSI 客户端配置(Windows) 客户端环境 Client :Windows 7 ip :192.168.121.138 一.首先查看客户端本地所有的磁盘 查看路径:邮件计算 ...