洛谷P3233 世界树 [HNOI2014] 虚树

2023-03-02,

正解:虚树

解题报告:

传送门!

首先看到这种就要想到虚树这个是毫无疑问的QwQ

建虚树什么的都可以循规蹈矩地做,不说辣,具体可以看下虚树学习笔记什么的看下板子

但是建好虚树之后怎么搞还是有点儿讲究,所以专门开个题解港下(所以为什么好多人拿这个当做虚树入门题什么的昂,,,大概我太菜辣所以只有理解了半天趴QAQ(我我我我在网上找题解看到好多都是说,"首先求值很容易balabala",,,就很绝望,,,可能真的我太菜辣QAQ

首先读入dfs然后建虚树什么的跳过,直接到计算答案的地方

这里主要分为两大部分,分别港下w

(然后为了表述方便,先define一下,x的被管辖点y指y管x

首先找到虚树上每个节点的被管辖点(编号&距离

考虑到虚树上的点有两个情况,第一个是管辖点,显然属于自己,不说

然后如果不是管辖点,考虑到一定要么在子树中,要么不在子树中(...好废话啊)

如果在子树中,可以递归下去更新就好

如果不在子树中,考虑到管辖点和它的路径一定通过与父亲节点的连边,所以可以通过父亲节点的最近点更新

但是考虑到如果我们在做dfs的时候是先枚举儿子节点再计算这个点的结果的(实现算出管辖点在子树中的情况w

所以在做点x的时候实际上x的父亲的结果是还麻油求出来的

所以再另外dfs一遍就好了

然后关于这个还有另外一种理解方法,实现是一样的但是我还是大概港下QAQ

就是任意一条路径必然是向上走几步(也可以不走)再向下走几步

所以可以分别求出来向上向下,也就是我上面说的分别求出子树中和通过父亲节点w

(一个小技巧是由题意可得这个信息是以距离为第一关键字编号为第二关键字,所以用pair就很susi!

(虽然这个小技巧挺显然的我不说应该也能知道QAQ

upd:

打完代码之后发现有个之前麻油注意到的点,,,就是因为我们后面都是用的size昂这种的,就都是算的它子树里面的,然后如果有一个情况,给定的点都在最初dfs的根节点的一侧,那么另一侧的点就都统计不到辣(这个样例给得挺给力的,我就是测样例的时候发现的这个问题w

所以解决方法是在建虚树的时候直接强制性给它加入1号节点(就是最初dfs的根结点,因为我习惯直接从1号节点开始所以我是直接加1号

然后因为我是打完了代码才发现的这个问题,就不想对虚树进行大改了,所以就直接在原来的基础上判断一下有麻油1号,麻油直接强制加入就好,然后输出的时候注意特判一下不要输出多了/少了就好QwQ

il void dfs_fk_dtu(ll x)
{
if(ext[x])bl[x]=mp(,x);else bl[x]=mp(inf,);
e_fk(i,x){dfs_fk_dtu(t_fk(i));bl[x]=min(bl[x],mp(bl[t_fk(i)].fr+dep[t_fk(i)]-dep[x],bl[t_fk(i)].sc));}
}
il void dfs_fk_utd(ll x,ll dis,ll nm){if(bl[x]>mp(dis,nm))bl[x]=mp(dis,nm);e_fk(i,x)dfs_fk_utd(t_fk(i),bl[x].fr+dep[t_fk(i)]-dep[x],bl[x].sc);}

这儿是代码w

然后计算每个管辖点管了多少个节点

现在考虑虚树上的点x,它的被管辖点是y,然后现在考虑它在实树上的儿子节点z,然后就花式分类讨论昂

可以想到如果z的子树中麻油管辖点,那么整棵z的被管辖点都是y,就可以ans+=size[z]

然后如果有管辖点,就比较难算,可以先当做整棵z的被管辖点都不是y,之后再把是y的加上,等下另说

那么实现就是直接在虚树上做,对虚树上的点x,首先被管辖点y的ans是+=size[x]的,然后枚举它在虚树上的儿子节点z,对所有儿子节点倍增跳到实树上的x节点的儿子节点(x-y那条链上的儿子节点昂

这时候考虑到我们把含有z的整棵子树都减辣,显然是不对的,反例都懒得举辣太显然辣,,,

所以就把减多了的加上来就好

所以就枚虚树上的边,对于边两侧的两点,如果他们的被管辖点是一样的,那在前面的计算中相当于减了又加了,不管

如果不一样,它们两个之间的点就都麻油被计入答案中

这里随便解释一下趴还是比较显然的,,,?

令这条边两侧的点是x和y,且x是y的父亲

那么y中的节点肯定在第一步中给y的被管辖点提供贡献辣(如果y中又有关键点什么的不要在意!当做解决辣就是!子问题而已!)

x子树中除了y的所有节点肯定在第一步中给x的被管辖点提供答案辣(x中有其他关键点也同上括号

综上,就只会有x到y之间的点麻油计入答案辣

所以就倍增跳啊跳地算出来就可以辣

最后说一下,,,看起来这儿又是要两次dfs的,其实显然一次就足够,所以我打的是一次dfs的w

il void dfs_cal(ll x)
{
ll siz=sz[x];
e_fk(i,x)
{
ll sn=chd(t_fk(i),x);dfs_cal(t_fk(i));
siz-=sz[sn];
if(bl[t_fk(i)]==bl[x])siz+=sz[sn]-sz[t_fk(i)];
else
{
ll tmp=t_fk(i);
my(j,,)
if(mp(dep[t_fk(i)]-dep[fa[tmp][j]]+bl[t_fk(i)].fr,bl[t_fk(i)].sc)<mp(dep[fa[tmp][j]]-dep[x]+bl[x].fr,bl[x].sc))tmp=fa[tmp][j];
as[bl[t_fk(i)].sc]+=sz[tmp]-sz[t_fk(i)];siz+=sz[sn]-sz[tmp];
}
bl[t_fk(i)]=mp(inf,);
}
as[bl[x].sc]+=siz;
head_fk[x]=;
}

不建议直接复制粘贴,因为这个思想挺好的,还是自己打一下加深理解为好QAQ而且也麻油很多,自己好好理解然后打出来成就感还是max的呢!

综上,这道题就做完辣,最后只要把最初的构建虚树之类的代码和上面两个代码拼接起来就打完正解辣!

代码可能有点丑陋(压行+一堆宏定义+丑陋命名,我自己都有点嫌弃,,,我错了下次还敢TT

#include<bits/stdc++.h>
using namespace std;
#define il inline
#define rg register
#define ll long long
#define fr first
#define sc second
#define gc getchar()
#define mp make_pair
#define t_rl(i) edge_rl[i].to
#define t_fk(i) edge_fk[i].to
#define w_rl(i) edge_rl[i].wei
#define w_fk(i) edge_fk[i].wei
#define rp(i,x,y) for(rg ll i=x;i<=y;++i)
#define my(i,x,y) for(rg ll i=x;i>=y;--i)
#define e_rl(i,x) for(rg ll i=head_rl[x];i;i=edge_rl[i].nxt)
#define e_fk(i,x) for(rg ll i=head_fk[x];i;i=edge_fk[i].nxt) const ll N=+,M=+,inf=1e12;
ll edge_rl_cnt,edge_fk_cnt,head_rl[N],head_fk[M],dep[N],fa[M][],n,m,ld_cnt,land[M],cnt_dfn,dfn[N],stck[N],head_stck,output[M],rt,as[N],sz[N];
bool ext[M];
pair<ll,ll>bl[N];
struct ed{ll to,nxt,wei;}edge_rl[N<<],edge_fk[M]; il ll read()
{
rg char ch=gc;rg ll x=;rg bool y=;
while(ch!='-' && (ch>'' || ch<''))ch=gc;
if(ch=='-')ch=gc,y=;
while(ch>='' && ch<='')x=(x<<)+(x<<)+(ch^''),ch=gc;
return y?x:-x;
}
il void ad_rl(rg ll x,rg ll y,rg ll z){edge_rl[++edge_rl_cnt]=(ed){y,head_rl[x],z};head_rl[x]=edge_rl_cnt;}
il void ad_fk(rg ll x,rg ll y,rg ll z){edge_fk[++edge_fk_cnt]=(ed){y,head_fk[x],z};head_fk[x]=edge_fk_cnt;}
void dfs_rl(ll nw,ll fat)
{
dfn[nw]=++cnt_dfn;
dep[nw]=dep[fat]+;sz[nw]=;fa[nw][]=fat;rp(i,,)fa[nw][i]=fa[fa[nw][i-]][i-];
e_rl(i,nw)if(t_rl(i)^fat)dfs_rl(t_rl(i),nw),sz[nw]+=sz[t_rl(i)];
}
il bool cmp(ll gd,ll gs){return dfn[gd]<dfn[gs];}
il ll lca(ll x,ll y)
{
if(dep[x]<dep[y])swap(x,y);
my(i,,)if(dep[fa[x][i]]>=dep[y])x=fa[x][i];
if(x==y)return x;
my(i,,)if(fa[x][i]!=fa[y][i])x=fa[x][i],y=fa[y][i];
return fa[x][];
}
il ll chd(ll x,ll y){my(i,,)if(dep[fa[x][i]]>dep[y])x=fa[x][i];return x;}
il void build_fk()
{
head_stck=edge_fk_cnt=;
sort(land+,land++ld_cnt,cmp);
rp(i,,ld_cnt)
{
if(!head_stck)stck[++head_stck]=land[i];
else
{
ll grd=lca(stck[head_stck],land[i]);
if(grd==stck[head_stck]){stck[++head_stck]=land[i];continue;}
while(dep[stck[head_stck-]]>dep[grd])
ad_fk(stck[head_stck-],stck[head_stck],dep[stck[head_stck-]]-dep[stck[head_stck]]),--head_stck;
if(grd!=stck[head_stck-])ad_fk(grd,stck[head_stck],dep[grd]-dep[stck[head_stck]]),stck[head_stck]=grd,stck[++head_stck]=land[i];
else ad_fk(grd,stck[head_stck],dep[grd]-dep[stck[head_stck]]),stck[head_stck]=land[i];
}
}
while(head_stck>)ad_fk(stck[head_stck-],stck[head_stck],dep[stck[head_stck-]]-dep[stck[head_stck]]),--head_stck;rt=stck[head_stck];
}
il void dfs_fk_dtu(ll x)
{
if(ext[x])bl[x]=mp(,x);else bl[x]=mp(inf,);
e_fk(i,x){dfs_fk_dtu(t_fk(i));bl[x]=min(bl[x],mp(bl[t_fk(i)].fr+dep[t_fk(i)]-dep[x],bl[t_fk(i)].sc));}
}
il void dfs_fk_utd(ll x,ll dis,ll nm){if(bl[x]>mp(dis,nm))bl[x]=mp(dis,nm);e_fk(i,x)dfs_fk_utd(t_fk(i),bl[x].fr+dep[t_fk(i)]-dep[x],bl[x].sc);}
il void dfs_cal(ll x)
{
ll siz=sz[x];
e_fk(i,x)
{
ll sn=chd(t_fk(i),x);dfs_cal(t_fk(i));
siz-=sz[sn];
if(bl[t_fk(i)]==bl[x])siz+=sz[sn]-sz[t_fk(i)];
else
{
ll tmp=t_fk(i);
my(j,,)
if(mp(dep[t_fk(i)]-dep[fa[tmp][j]]+bl[t_fk(i)].fr,bl[t_fk(i)].sc)<mp(dep[fa[tmp][j]]-dep[x]+bl[x].fr,bl[x].sc))tmp=fa[tmp][j];
as[bl[t_fk(i)].sc]+=sz[tmp]-sz[t_fk(i)];siz+=sz[sn]-sz[tmp];
}
bl[t_fk(i)]=mp(inf,);
}
as[bl[x].sc]+=siz;
head_fk[x]=;
}
il void cal()
{
dfs_fk_dtu(rt);dfs_fk_utd(rt,bl[rt].fr,bl[rt].sc);
/*以上都是正确dei!然而就这个cal出错辣1551*/
if(!ext[])--ld_cnt;
dfs_cal(rt);rp(i,,ld_cnt)printf("%lld ",as[output[i]]),as[output[i]]=,ext[output[i]]=;printf("\n");
} int main()
{
freopen("sjs.in","r",stdin);freopen("sjs.out","w",stdout);
n=read();rp(i,,n-){ll x=read(),y=read();ad_rl(x,y,);ad_rl(y,x,);}dfs_rl(,);
m=read();rp(i,,m){ld_cnt=read();rp(i,,ld_cnt)ext[output[i]=land[i]=read()]=;if(!ext[])land[++ld_cnt]=;build_fk();;cal();}
return ;
}
/*
,,,感jio自己命名有点儿玄学,,,在后边解释下好了quq
其实也麻油几个要解释的呢QAQ?
一个是rl-real,表示最开始的实树
一个是fk-fake,表示后来建起来的虚树
一个是utd-uptodown,表示从上往下更新
一个是dtu-downtoup,表示从下往上更新
好像也麻油辣?
*/
/*清零操作不要忘了,,,调死我了,,,wsl*/

这儿是代码,,,打死我了TT(然后灵巧的蛇皮命名方式写在后面辣!

洛谷P3233 世界树 [HNOI2014] 虚树的相关教程结束。