传送门

前言

一年一度,生长在高山上的雪松果树又结果了。

第二天,雪松果树长成了一颗参天大树, 上面长满了雪松果。

求雪松果树生长周期

整活向题解。

奋力卡常 3h,纪念一下。

是的,我一个人的提交占了三页。

题意简述

给一棵树,查询某节点的 \(k\)-cousin。

题解

基本思路

虽然有很多更优的做法,但是我们考虑线段树合并。

在每个节点建一棵权值线段树,维护子树内每个深度的节点个数。

把询问离线下来,对整棵树进行 dfs,将每棵子树的线段树合并到该节点的线段树,之后处理该节点的询问。

这里需要将询问转化为求原询问节点的 \(k\)-father 的 \(k\)-son 数量减一。

求 \(k\)-father 可以使用倍增。

于是我们可以写出如下代码:

#include <cstdio>
#include <vector>
#define N 1000005
int n,q,ans[N],fa[N][21],rt[N];
int hed[N],tal[N],nxt[N],cnte;
void adde(int u,int v) {tal[++cnte]=v,nxt[cnte]=hed[u],hed[u]=cnte;}
struct query {int id,k;};
std::vector<query> a[N];
struct sgt
{
#define mid (lb+rb>>1)
#define pushup(x) d[x]=d[ls[x]]+d[rs[x]]
int d[N<<5],ls[N<<5],rs[N<<5],idx;
void modify(int &x,int t,int lb,int rb)
{
if(!x) x=++idx;
d[x]++;
if(lb==rb) return;
if(t<=mid) modify(ls[x],t,lb,mid);
else modify(rs[x],t,mid+1,rb);
}
int query(int x,int t,int lb,int rb)
{
if(!x) return 0;
if(lb==rb) return d[x];
if(t<=mid) return query(ls[x],t,lb,mid);
else return query(rs[x],t,mid+1,rb);
}
int merge(int x,int y,int lb,int rb)
{
if(!x||!y) return x+y;
if(lb==rb) {d[x]+=d[y];return x;}
ls[x]=merge(ls[x],ls[y],lb,mid);
rs[x]=merge(rs[x],rs[y],mid+1,rb);
pushup(x);return x;
}
#undef mid
#undef pushup
} tr;
void dfs(int x,int f,int dep)
{
tr.modify(rt[x],dep,1,n);
for(int i=hed[x];i;i=nxt[i]) if(tal[i]!=f)
dfs(tal[i],x,dep+1),rt[x]=tr.merge(rt[x],rt[tal[i]],1,n);
for(int i=0;i<a[x].size();i++)
ans[a[x][i].id]=tr.query(rt[x],dep+a[x][i].k,1,n)-1;
}
main()
{
scanf("%d%d",&n,&q);
for(int i=2;i<=n;i++) scanf("%d",&fa[i][0]),adde(fa[i][0],i);
for(int i=1;i<=20;i++) for(int j=2;j<=n;j++)
fa[j][i]=fa[fa[j][i-1]][i-1];
for(int i=1;i<=q;i++)
{
int u,k;
scanf("%d%d",&u,&k);
int d=0;
for(int j=20;j>=0;j--)
if(d+(1<<j)<=k) u=fa[u][j],d+=1<<j;
if(u) a[u].push_back({i,k});
}
dfs(1,0,1);
for(int i=1;i<=q;i++) printf("%d ",ans[i]);
}

像这样。

优化

如果你按照我们刚才的思路写出了如上代码,那么恭喜你,你可以获得 \(40\) 分的好成绩。

于是我们需要优化。

由于剩下的点都 MLE 了,所以需要优化空间。

首先,注意到线段树的数组开得很大,这是因为每个点都有一棵线段树。然而我们线段树合并统计答案时,当一个节点的答案被合并到父节点时,这个节点的线段树就再也用不上了。所以我们回收这棵线段树上的节点。这样,线段树的空间只需开到 \(4e6\)。

于是得到这样一棵线段树:

struct sgt
{
#define mid (lb+rb>>1)
int d[N<<2],ls[N<<2],rs[N<<2],idx;
int st[M],tp; //回收
void modify(int &x,int t,int lb,int rb)
{
if(!x) x=tp?st[tp--]:++idx; //使用先前回收的空间
d[x]++;
if(lb==rb) return;
if(t<=mid) modify(ls[x],t,lb,mid);
else modify(rs[x],t,mid+1,rb);
}
int query(int x,int t,int lb,int rb)
{
if(!x) return 0;
if(lb==rb) return d[x];
if(t<=mid) return query(ls[x],t,lb,mid);
return query(rs[x],t,mid+1,rb);
}
int merge(int x,int y,int lb,int rb)
{
if(!x||!y) return x|y;
d[x]+=d[y];
if(lb<rb)
ls[x]=merge(ls[x],ls[y],lb,mid),
rs[x]=merge(rs[x],rs[y],mid+1,rb);
d[y]=ls[y]=rs[y]=0,st[++tp]=y; //回收空间
return x;
}
#undef mid
} tr;

其次,我们开了 \(1e6\) 个 vector 来存储询问,这样消耗的空间是无法接受的,所以需要改变询问的存储方式。

可以使用一种前向星式的做法:

int qh[N];		//链头
int qnxt[N]; //下一个询问的编号
struct query
{
int v,k; //v:u的k-father
} a[N];
void addq(int x)
{
qnxt[x]=qh[a[x].v];
qh[a[x].v]=x;
}

然后我们看到用来求 k-father 的倍增数组也占了很大的空间,于是我们改用树剖。

int dep[N],son[N],siz[N],top[N],dfn[N],li[N],id;
void dfs1(int x) //正常的树剖
{
siz[x]=1,dep[x]=dep[fa[x]]+1;
for(int i=hed[x];i;i=nxt[i])
if(!siz[tal[i]])
{
dfs1(tal[i]);
siz[x]+=siz[tal[i]];
if(siz[tal[i]]>siz[son[x]])
son[x]=tal[i];
}
}
void dfs2(int x,int tp) //正常的数剖
{
li[dfn[x]=++id]=x,top[x]=tp;
if(!son[x]) return;
dfs2(son[x],tp);
for(int i=hed[x];i;i=nxt[i])
if(!top[tal[i]])
dfs2(tal[i],tal[i]);
}
int anc(int u,int k)
{
int v=u;
while(v&&dep[u]-dep[top[v]]<k)
v=fa[top[v]];
if(!v) return 0; //没有k-father
/*
例:u=8,k=4,dep[8]=7
跳到了重链1-6
1-2-3-4-5-6
^ ^ v
top kfa dep[3]=dep[u]-k
dfn[1]+dep[3]-dep[1]=dfn[3]
li[dfn[3]]=3
*/
return li[dfn[top[v]]+dep[u]-k-dep[top[v]]];
}

像这样。

终极优化

如果你使用如上方式优化,那么恭喜你,可以不再 MLE 并获得 \(76\) 分至 \(92\) 分不等的好成绩。

当然,如果你的写法常数更小卡过去了也行。

为什么不能 AC 呢?

让我们看看最初的代码中调用线段树的部分:

rt[x]=tr.merge(rt[x],rt[tal[i]],1,n);

线段树的值域是 \(n\)。

然而,我们的线段树维护的是深度。

所以值域应该为深度的最大值。

代码

#include <cstdio>
#define N 1000002 int n,q,md,ans[N],fa[N],rt[N];
int hed[N],tal[N],nxt[N],cnte;
void adde(int u,int v) {tal[++cnte]=v,nxt[cnte]=hed[u],hed[u]=cnte;}
int qh[N],qnxt[N];
int av[N],ak[N];
void addq(int x) {qnxt[x]=qh[av[x]],qh[av[x]]=x;}
int dep[N],son[N],siz[N],top[N],dfn[N],li[N],id;
void dfs1(int x)
{
siz[x]=1,dep[x]=dep[fa[x]]+1;
if(dep[x]>md) md=dep[x];
for(int i=hed[x];i;i=nxt[i]) if(!siz[tal[i]])
{
dfs1(tal[i]),siz[x]+=siz[tal[i]];
if(siz[tal[i]]>siz[son[x]]) son[x]=tal[i];
}
}
void dfs2(int x,int tp)
{
li[dfn[x]=++id]=x,top[x]=tp;
if(!son[x]) return;
dfs2(son[x],tp);
for(int i=hed[x];i;i=nxt[i]) if(!top[tal[i]]) dfs2(tal[i],tal[i]);
}
int anc(int u,int k)
{
int v=u;
while(v&&dep[u]-dep[top[v]]<k) v=fa[top[v]];
if(!v) return 0;
return li[dfn[top[v]]+dep[u]-k-dep[top[v]]];
}
struct sgt
{
#define mid (lb+rb>>1)
int d[N<<2],ls[N<<2],rs[N<<2],idx;
int st[N<<2],tp;
void modify(int &x,int t,int lb,int rb)
{
if(!x) x=tp?st[tp--]:++idx;
d[x]++;
if(lb==rb) return;
if(t<=mid) modify(ls[x],t,lb,mid);
else modify(rs[x],t,mid+1,rb);
}
int query(int x,int t,int lb,int rb)
{
if(!x) return 0;
if(lb==rb) return d[x];
if(t<=mid) return query(ls[x],t,lb,mid);
return query(rs[x],t,mid+1,rb);
}
int merge(int x,int y,int lb,int rb)
{
if(!x||!y) return x|y;
d[x]+=d[y];
if(lb<rb)
ls[x]=merge(ls[x],ls[y],lb,mid),
rs[x]=merge(rs[x],rs[y],mid+1,rb);
d[y]=ls[y]=rs[y]=0,st[++tp]=y;
return x;
}
#undef mid
} tr;
void dfs3(int x)
{
tr.modify(rt[x],dep[x],1,md);
for(int i=hed[x];i;i=nxt[i]) if(tal[i]^fa[x])
dfs3(tal[i]),rt[x]=tr.merge(rt[x],rt[tal[i]],1,md);
for(int i=qh[x];i;i=qnxt[i])
ans[i]=tr.query(rt[x],dep[x]+ak[i],1,md)-1;
}
main()
{
scanf("%d%d",&n,&q);
for(int i=2;i<=n;i++) scanf("%d",&fa[i]),adde(fa[i],i);
dfs1(1),dfs2(1,1);
for(int i=1;i<=q;i++)
{
int u,k;
scanf("%d%d",&u,&k);
av[i]=anc(u,k),ak[i]=k,addq(i);
}
dfs3(1);
for(int i=1;i<=q;i++) printf("%d ",ans[i]);
}

\[\Huge End
\]

P5384 [Cnoi2019] 雪松果树 题解的更多相关文章

  1. P5384[Cnoi2019]雪松果树 (长链剖分)

    题面 一棵以 1 1 1 为根的 N N N 个节点的有根树, Q Q Q 次询问,每次问一个点 u u u 的 k k k 级兄弟有多少个(第 k k k 代祖先的第 k k k 代孩子),如果没有 ...

  2. [Luogu5384][Cnoi2019] 雪松果树

    传送门 虽然这题是一道二合一,也不算难,但还是学到了很多东西啊,\(k\) 级儿子个数的五种求法!!我还是觉得四种比较好( \(k\) 级儿子个数有五种求法,你知道么? --鲁迅 首先 \(k\) 级 ...

  3. LOJ6276:果树——题解

    https://loj.ac/problem/6276#submit_code NiroBC 姐姐是个活泼的少女,她十分喜欢爬树,而她家门口正好有一棵果树,正好满足了她爬树的需求.这颗果树有N 个节点 ...

  4. 题解 洛谷 P5385 【[Cnoi2019]须臾幻境】

    首先我们知道 \(n\) 个点的树有 \(n-1\) 条边,因此对于森林来说,其点数减边数即为树的个数.那么对于普通的图,求出其任意一个生成树森林,森林中树的个数即为原图中连通块的个数,也就是点数减边 ...

  5. HAOI2018 简要题解

    这套题是 dy, wearry 出的.学长好强啊,可惜都 \(wc\) 退役了.. 话说 wearry 真的是一个计数神仙..就没看到他计不出来的题...每次考他模拟赛总有一两道毒瘤计数TAT 上午的 ...

  6. ccf-csp201909题解

    目录 ccf-csp201909题解 1. 201909-1 小明种苹果 题目描述 解析 通过代码 2. 201909-2 小明种苹果(续) 题目描述 解析 通过代码 3. 201909-3 字符画 ...

  7. 2016 华南师大ACM校赛 SCNUCPC 非官方题解

    我要举报本次校赛出题人的消极出题!!! 官方题解请戳:http://3.scnuacm2015.sinaapp.com/?p=89(其实就是一堆代码没有题解) A. 树链剖分数据结构板题 题目大意:我 ...

  8. noip2016十连测题解

    以下代码为了阅读方便,省去以下头文件: #include <iostream> #include <stdio.h> #include <math.h> #incl ...

  9. BZOJ-2561-最小生成树 题解(最小割)

    2561: 最小生成树(题解) Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 1628  Solved: 786 传送门:http://www.lyd ...

  10. Codeforces Round #353 (Div. 2) ABCDE 题解 python

    Problems     # Name     A Infinite Sequence standard input/output 1 s, 256 MB    x3509 B Restoring P ...

随机推荐

  1. Windows下的终端工具-Terminal

    文档:https://docs.microsoft.com/zh-cn/windows/terminal/ Windows 终端是一个面向命令行工具和 shell(如命令提示符.PowerShell ...

  2. qiankun 的 JS 沙箱隔离机制

    为什么需要JS沙箱 想象一下 当一个应用(比如应用 A)加载时,可能会对 window 对象的属性进行修改或添加.如果不加控制,这些修改会影响到之后加载的其他应用(比如应用 B),就会导致属性读写冲突 ...

  3. Vue.js 文本行滚动

    1.前言 文本行滚动组件,效果如图 2.封装思路 封装一个组件,接收一个数组,每个数组元素就是一个段文本 组件使用httpVueLoader进行封装加载 使用css位移,配合过渡效果才展示动画 滚动逻 ...

  4. vite2+vue3使用tsx报错React is not defined、h is not defined

    vite 为 .jsx 和 .tsx 文件提供开箱即用支持. 如果不是在 react 中使用 jsx,对于报错: React is not defined 需要在 vite.config.js 文件中 ...

  5. 【原创】ARM64 实时linux操作系xenomai4(EVL)构建安装简述

    目录 0 环境说明 1 内核构建 2 库编译 方式1 交叉编译 方式2 本地编译 3 测试 单元测试 hectic:EVL 上下文切换 latmus:latency测试 4 RK3588 xenoma ...

  6. 聊一聊 C#后台线程 如何阻塞程序退出

    一:背景 1. 讲故事 这篇文章起源于我的 C#内功修炼训练营里的一位朋友提的问题:后台线程的内部是如何运转的 ? ,犹记得C# Via CLR这本书中 Jeffery 就聊到了他曾经给别人解决一个程 ...

  7. 【自媒体直播】手机摄像电脑控制OBS多平台推流解决方案

    1.准备iriun 官网:Iriun 这个软件是免费的,不过会有水印.你需要在官网下载安装包进行安装,有windows系统版本和MAC系统版本.Ubuntu版本可以选择,根据自己电脑系统版本选择. 这 ...

  8. Qt开源作品39-日志输出增强版V2022

    一.前言 之前已经开源过基础版本,近期根据客户需求和自己的项目需求,提炼出通用需求部分,对整个日志重定向输出类重新规划和重写代码. 用Qt这个一站式超大型GUI超市做开发已经十二年了,陆陆续续开发过至 ...

  9. Kubernetes系列(三) - 通过Kubeadm部署kubernetes

    目录 1. Kubeadm简介 2. 本次操作的机器配置 3. 部署步骤 3.1 准备工作 3.2 安装docker 3.3 安装kubeadm, kubectl, kubelet 3.4 maste ...

  10. C#/.NET/.NET Core技术前沿周刊 | 第 19 期(2024年12.23-12.29)

    前言 C#/.NET/.NET Core技术前沿周刊,你的每周技术指南针!记录.追踪C#/.NET/.NET Core领域.生态的每周最新.最实用.最有价值的技术文章.社区动态.优质项目和学习资源等. ...