洛谷题面传送门

好家伙,在做这道题之前我甚至不知道有个东西叫树分块

树分块,说白了就是像对序列分块一样设一个阈值 \(B\),然后在树上随机撒 \(\dfrac{n}{B}\) 个关键点,满足任意一个点到距离其最近的关键点距离不超过 \(\mathcal O(B)\) 级别,这样我们就可以预处理关键点两两之间的信息,然后询问两个点路径上的信息时直接将预处理的信息拿出来使用,再额外加上两个端点到距离它们最近的关键点之间的路径的贡献即可算出答案,复杂度 \(\mathcal O(B^2+qB+\dfrac{n^2}{B})\),一般 \(B\) 取 \(\sqrt{n}\)

当然这个“随机撒点”也并不用什么高超的玄学技巧,甚至不用随机()。一个很显然的想法是将深度 \(\bmod B=0\) 设为关键点,但稍微懂点脑子即可构造出反例:一条长度为 \(B\) 的链下面挂 \(n-B\) 个叶子。因此考虑对上面的过程做点手脚,我们记 \(dis_i\) 为 \(i\) 到距离它最近的叶子节点的距离(学过左偏树的同学应该对这个定义感到很熟悉),那么我们将深度 \(\bmod B=0\) 且 \(dis_i\ge B\) 的点 \(i\) 设为关键节点即可,容易证明在这种构造方法下关键点个数是严格 \(\dfrac{n}{B}\) 级别的,读者自证不难,复杂度也就得到了保证。

接下来考虑怎样求出答案,我们首先预处理出每堆关键节点路径上颜色的情况,用 bitset 维护,该操作显然可以以 \(\mathcal O(\dfrac{n^2}{B})\) 的及 \(\mathcal O(\dfrac{n^3}{B^2\omega})\) 空间复杂度完成。那么对于一组询问 \(x,y\),记 \(x\) 祖先中离其最近的关键点为 \(fx\),\(fy\) 的定义类似,\(l=\text{LCA}(x,y)\),分三种情况:

  • \(fx,fy\) 的深度均 \(<l\) 的深度,根据关键点的生成方式可知任意一条自上而下的长度为 \(2B\)(注意,这里不是 \(B\),因为对于叶子节点而言,其祖先中离其最近的关键节点与其的距离可能达到 \(2B\))的链上必有一个关键节点,因此 \(x\) 到 \(l\) 的距离必定小于 \(2B\),\(y\) 同理,对于这种情况暴力跳父亲不会出问题
  • \(fx,fy\) 中恰有一个深度 \(<l\) 的深度,那我们不妨设 \(fy\) 深度 \(<l\) 的深度,那么我们就找出 \(x\to l\) 这条链上离 \(l\) 最近的关键点 \(z\),具体方法是,我们倍增找出 \(x\) 的 \(dep_x-dep_l-2B\) 级祖先,然后暴力向上跳,每遇到一个关键点就将 \(z\) 设为关键节点,那么我们根据预处理的值找到 \(z\) 与 \(fx\) 之间的答案,然后加上 \(x\to fx,z\to l,y\to l\) 这三段的答案即可
  • \(fx,fy\) 深度均 \(\ge l\) 的深度,那么我们直接找出 \(fx,fy\) 之间的答案,然后加上 \(x\to fx,y\to fy\) 的答案即可。

时间复杂度 \(\mathcal O(\dfrac{n^2}{B}+qB+\dfrac{n^3}{B^2\omega})=\mathcal O(n\sqrt{n}+\dfrac{n^2}{\omega})\),常数略有点大,但实际跑起来不算慢(

const int MAXN=4e4;
const int LOG_N=16;
const int BLK=200;
int n,qu,a[MAXN+5],key[MAXN+5],uni[MAXN+5],num=0;
int hd[MAXN+5],to[MAXN*2+5],nxt[MAXN*2+5],ec=0;
void adde(int u,int v){to[++ec]=v;nxt[ec]=hd[u];hd[u]=ec;}
int id[MAXN+5],pcnt=0,blk,buc[MAXN+5];
int dis[MAXN+5],dep[MAXN+5],fa[MAXN+5][LOG_N+2];
bitset<MAXN+5> col[21000],vis;
void dfs0(int x,int f){
fa[x][0]=f;
for(int e=hd[x];e;e=nxt[e]){
int y=to[e];if(y==f) continue;dep[y]=dep[x]+1;
dfs0(y,x);chkmax(dis[x],dis[y]+1);
} if(dep[x]%blk==0&&dis[x]>=blk) id[x]=++pcnt;
}
int qwq[BLK+5][BLK+5],cc=0;
void dfs(int x,int f,int rt){
buc[a[x]]++;if(buc[a[x]]==1) vis.set(a[x]);
if(id[x]) col[qwq[id[rt]][id[x]]]=vis;
for(int e=hd[x];e;e=nxt[e]){
int y=to[e];if(y==f) continue;
dfs(y,x,rt);
} buc[a[x]]--;if(!buc[a[x]]) vis.reset(a[x]);
}
int getlca(int x,int y){
if(dep[x]<dep[y]) swap(x,y);
for(int i=LOG_N;~i;i--) if(dep[x]-(1<<i)>=dep[y]) x=fa[x][i];
if(x==y) return x;
for(int i=LOG_N;~i;i--) if(fa[x][i]^fa[y][i]) x=fa[x][i],y=fa[y][i];
return fa[x][0];
}
int jump(int x){
while(x){
if(id[x]) return x;
x=fa[x][0];
} return 0;
}
int get_kanc(int x,int k){
for(int i=LOG_N;~i;i--) if(k>>i&1) x=fa[x][i];
return x;
}
int main(){
// freopen("C:\\Users\\汤\\Downloads\\P6177_1.in","r",stdin);
// cout<<sizeof(col)<<endl;
scanf("%d%d",&n,&qu);
for(int i=1;i<=n;i++) scanf("%d",&a[i]),key[i]=a[i];
key[0]=-1;sort(key+1,key+n+1);
for(int i=1;i<=n;i++) if(key[i]^key[i-1]) uni[++num]=key[i];
for(int i=1;i<=n;i++) a[i]=lower_bound(uni+1,uni+num+1,a[i])-uni;
for(int i=1,u,v;i<n;i++) scanf("%d%d",&u,&v),adde(u,v),adde(v,u);
blk=(int)sqrt(n);dfs0(1,0);dep[0]=-1;
for(int i=1;i<=pcnt;i++) for(int j=1;j<=i;j++) qwq[i][j]=qwq[j][i]=++cc;
for(int i=1;i<=n;i++) if(id[i]) memset(buc,0,sizeof(buc)),dfs(i,0,i);
for(int i=1;i<=LOG_N;i++) for(int j=1;j<=n;j++)
fa[j][i]=fa[fa[j][i-1]][i-1];
int pre=0;
while(qu--){
int x,y;scanf("%d%d",&x,&y);x^=pre;vis.reset();
int l=getlca(x,y),fx=jump(x),fy=jump(y);
if(dep[fx]<dep[l]&&dep[fy]<dep[l]){
while(x!=l) vis.set(a[x]),x=fa[x][0];
while(y!=l) vis.set(a[y]),y=fa[y][0];
vis.set(a[l]);
printf("%d\n",pre=vis.count());
} else if(dep[fx]>=dep[l]&&dep[fy]>=dep[l]){
vis=col[qwq[id[fx]][id[fy]]];
while(x!=fx) vis.set(a[x]),x=fa[x][0];
while(y!=fy) vis.set(a[y]),y=fa[y][0];
printf("%d\n",pre=vis.count());
} else{
if(dep[fy]>=dep[l]) swap(x,y),swap(fx,fy);
assert(dep[fy]<dep[l]);
int z=get_kanc(x,max(dep[x]-dep[l]-(blk<<1|1),0));
int near=-1;
while(z!=l){
if(id[z]) near=z;
z=fa[z][0];
} if(id[l]) near=l;
assert(~near);
if(fx!=near) vis=col[qwq[id[fx]][id[near]]];
while(x!=fx) vis.set(a[x]),x=fa[x][0];
while(near!=l) vis.set(a[near]),near=fa[near][0];
while(y!=l) vis.set(a[y]),y=fa[y][0];
vis.set(a[l]);
printf("%d\n",pre=vis.count());
}
}
return 0;
}

洛谷 P6177 - Count on a tree II/【模板】树分块(树分块)的更多相关文章

  1. 洛谷P2633 Count on a tree(主席树,倍增LCA)

    洛谷题目传送门 题目大意 就是给你一棵树,每个点都有点权,每次任意询问两点间路径上点权第k小的值(强制在线). 思路分析 第k小......又是主席树了.但这次变成树了,无法直接维护前缀和. 又是树上 ...

  2. 洛谷P2633 Count on a tree(主席树,倍增LCA,树上差分)

    洛谷题目传送门 题目大意 就是给你一棵树,每个点都有点权,每次任意询问两点间路径上点权第k小的值(强制在线). 思路分析 第k小......又是主席树了.但这次变成树了,无法直接维护前缀和. 又是树上 ...

  3. 洛谷 P2633 Count on a tree

    P2633 Count on a tree 题目描述 给定一棵N个节点的树,每个点有一个权值,对于M个询问(u,v,k),你需要回答u xor lastans和v这两个节点间第K小的点权.其中last ...

  4. 洛谷P2633 Count on a tree(主席树上树)

    题目描述 给定一棵N个节点的树,每个点有一个权值,对于M个询问(u,v,k),你需要回答u xor lastans和v这两个节点间第K小的点权.其中lastans是上一个询问的答案,初始为0,即第一个 ...

  5. 【洛谷1501】[国家集训队] Tree II(LCT维护懒惰标记)

    点此看题面 大致题意: 有一棵初始边权全为\(1\)的树,四种操作:将两点间路径边权都加上一个数,删一条边.加一条新边,将两点间路径边权都加上一个数,询问两点间路径权值和. 序列版 这道题有一个序列版 ...

  6. 洛谷P2633 Count on a tree

    题目描述 给定一棵N个节点的树,每个点有一个权值,对于M个询问(u,v,k),你需要回答u xor lastans和v这两个节点间第K小的点权.其中lastans是上一个询问的答案,初始为0,即第一个 ...

  7. 【洛谷 P1501】 [国家集训队]Tree II(LCT)

    题目链接 Tree Ⅱ\(=\)[模板]LCT+[模板]线段树2.. 分别维护3个标记,乘的时候要把加法标记也乘上. 还有就是模数的平方刚好爆\(int\),所以开昂赛德\(int\)就可以了. 我把 ...

  8. 洛谷 P2633 Count on a tree 主席树

    在一棵树上,我们要求点 $(u,v)$ 之间路径的第$k$大数. 对于点 $i$  ,建立 $i$  到根节点的一棵前缀主席树. 简单容斥后不难得出结果为$sumv[u]+sumv[v]−sumv[l ...

  9. 洛谷 P2633 Count on a tree 题解

    题面 对于每个点建立一颗主席树: 然后按照树上差分的思想统计主席树的前缀和: lca+主席树+前向星存表就可以了: #include <bits/stdc++.h> #define inc ...

随机推荐

  1. cassandra表中主键的类型

    cassandra表中主键的类型及区分? 一.类型及区分 二.参考文章 一.类型及区分 Cassandra的4种Key Primary Key 主键 Composite Key,Compound Ke ...

  2. vue3.x组件间通信,实用小技巧都在这里

    本想简单写写,没想到说清楚已经变成了一篇很长的帖子,欢迎当笔记搜藏起来. props / emits 父子组件通信 props一般负责向子组件传递数据 下面是一个简单的例子,父组件向子组件传递了一个t ...

  3. vue3.x全局$toast、$message、$loading等js插件

    有时候我们需要使用一些类似toast,messge.loading这些跟js交互很频繁的插件,vue3.x这类插件的定义跟vue2.x插件稍大,而且相对变得复杂了一点点. 第一种.需要时创建,用完移除 ...

  4. Coursera Deep Learning笔记 序列模型(二)NLP & Word Embeddings(自然语言处理与词嵌入)

    参考 1. Word Representation 之前介绍用词汇表表示单词,使用one-hot 向量表示词,缺点:它使每个词孤立起来,使得算法对相关词的泛化能力不强. 从上图可以看出相似的单词分布距 ...

  5. the Agiles Scrum Meeting 11

    会议时间:2020.4.20 20:00 1.每个人的工作 在这次例会上,我们对上周完成的工作进行了总结. 本周已完成的工作 个人结对项目增量开发组 tq: 创建广播功能 修复纯英文数字可能溢出bug ...

  6. linux shell 基本语法之快速上手shell编程

    从程序员的角度来看, Shell本身是一种用C语言编写的程序,从用户的角度来看,Shell是用户与Linux操作系统沟通的桥梁.用户既可以输入命令执行,又可以利用 Shell脚本编程,完成更加复杂的操 ...

  7. 第01课 OpenGL窗口(3)

    接下来的代码段创建我们的OpenGL窗口.我花了很多时间来做决定是否创建固定的全屏模式这样不需要许多额外的代码,还是创建一个容易定制的友好的窗口但需要更多的代码.当然最后我选择了后者.我经常在EMai ...

  8. 访问kubernetes CRD的几种方式

    访问kubernetes CRD的几种方式 最近在使用代码操作VictoriaMetrics Operator的CRD资源的过程中,探究了集中访问CRD资源的方式.下面以VictoriaMetrics ...

  9. 获取鼠标在 canvas 中的位置

    一般情况 一般情况下,如果需要在 canvas 中获取鼠标指针坐标,可以通过监听鼠标的 mousemove(如果只需单击时的坐标,可以用 click)事件. 当事件被触发时,我们可以获取鼠标相对于 v ...

  10. 基于Mui与H5+开发webapp的Android原生工程打包步骤(使用新版本5+SDK与Android studio)(部分内容转自dcloud官网)

    文章背景: dcloud官网给出的打包步骤对于有一定安卓打包基础的同学来说比较容易掌握,但是对于webapp小白来讲有的地方可能没有说的太具体.下面我给大家介绍的详细一点,保证大家按照步骤就能学会打包 ...