[HNOI2016]树(可持久化线段树+树上倍增)
[HNOI2016]树(可持久化线段树+树上倍增)
题面
给出一棵n个点的模板树和大树,根为1,初始的时候大树和模板树相同。接下来操作m次,每次从模板树里取出一棵子树,把它作为新树里节点y的儿子。操作完之后有q个询问,询问新树上两点之间的距离
\(n,m,q \leq 1 \times 10^5\)
分析
显然直接把所有节点存下来是不行的,因为节点的个数最多可以到\(10^{10}\)。发现本质不同的子树只有n个,我们考虑把子树缩成一个点,构造一棵新树。
新树上的点有3种编号,注意区分:
1.小节点的询问编号,即图中淡黑色数字(1~9),询问和加点的时候被输入,可能会爆int
2.所在大节点编号,即图中加粗的黑色数字
3.在模板树上对应的点的编号,即图中绿色数字
定义两个大节点之间的边权为大节点对应子树的树根和被挂上的节点在模板树上的距离+1。每一个大节点需要存储:
1.子树内小节点询问编号的范围idl[x],idr[x]
2.根节点在模板树上对应的点的编号from[x]
3.这棵子树接到的节点的询问编号link[x]
然后写两个函数
1.get_root(x) 找到询问编号为x的节点所在大节点的编号。只需要二分答案,找到满足idl[k]<=x的最小k即可
2.get_tpid(x) 找到询问编号为x的节点在模板树上的编号。由于新加入大树的结点是按照在模板树中编号的顺序重新编号,那么他们的大小顺序不变。我们先找到x所在大节点rt,再找到rt在模板树上对应的点的编号from[rt],显然答案是from[rt]的子树中第x-idl[rt]+1小的节点编号。只需要在模板树上按照dfs序建出主席树,维护编号的出现情况即可。
然后在大节点构成的树上进行树上倍增,维护lca和x往上走2^i步的边权和。
准备工作已经做完,我们来考虑如何求(x,y)距离
如图,我们先加上模板树上x到rtx的距离,然后在新树上像求lca一样往上跳,同时累计距离。直到x,y的父亲相同。
但是这里跳到最后一步的时候会出问题。如图,我们求9到5的距离,从rty直接跳到rty的父亲1会出问题,因为这样并不是最短路径,应该从rty跳到link[rty]才对,所以最后一步特判一下即可,x,y最后一步的距离为1+1+模板树上link[x]到link[y]的距离,其中1表示从a跳到link[a]的距离为1
本题细节很多,建议写完后先静态查错一遍,再对拍。本篇最末尾有数据生成器代码,欢迎使用。
代码
AC代码
//大毒瘤!!!!!!!
//
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define maxn 100000
#define maxlogn 20
using namespace std;
typedef long long ll;
int n,m,q;
struct persist_segment_tree{
#define lson(x) tree[x].ls
#define rson(x) tree[x].rs
struct node{
int ls;
int rs;
int cnt;
}tree[maxn*maxlogn+5];
int root[maxn+5];
int ptr;
void push_up(int x){
tree[x].cnt=tree[lson(x)].cnt+tree[rson(x)].cnt;
}
void update(int &x,int last,int upos,int l,int r){
x=++ptr;
tree[x]=tree[last];
if(l==r){
tree[x].cnt++;
return;
}
int mid=(l+r)>>1;
if(upos<=mid) update(tree[x].ls,tree[last].ls,upos,l,mid);
else update(tree[x].rs,tree[last].rs,upos,mid+1,r);
push_up(x);
}
int query(int xl,int xr,int k,int l,int r){
if(l==r) return l;
int mid=(l+r)>>1;
int cnt=tree[lson(xr)].cnt-tree[lson(xl)].cnt;
if(k<=cnt) return query(tree[xl].ls,tree[xr].ls,k,l,mid);
else return query(tree[xl].rs,tree[xr].rs,k-cnt,mid+1,r);
}
#undef lson
#undef rson
}CT;
namespace template_tree{
struct edge{
int from;
int to;
int next;
}E[maxn*2+5];
int esz=1;
int head[maxn+5];
void add_edge(int u,int v){
esz++;
E[esz].from=u;
E[esz].to=v;
E[esz].next=head[u];
head[u]=esz;
esz++;
E[esz].from=v;
E[esz].to=u;
E[esz].next=head[v];
head[v]=esz;
}
int log2n;
int anc[maxn+5][maxlogn+5];
int deep[maxn+5];
int tim;
int dfnl[maxn+5],dfnr[maxn+5];
int hash_dfn[maxn+5];
void dfs(int x,int fa){
deep[x]=deep[fa]+1;
dfnl[x]=++tim;
hash_dfn[tim]=x;
anc[x][0]=fa;
for(int i=1;i<=log2n;i++) anc[x][i]=anc[anc[x][i-1]][i-1];
for(int i=head[x];i;i=E[i].next){
int y=E[i].to;
if(y!=fa){
dfs(y,x);
}
}
dfnr[x]=tim;
}
int lca(int x,int y){
if(deep[x]<deep[y]) swap(x,y);
for(int i=log2n;i>=0;i--){
if(deep[anc[x][i]]>=deep[y]){
x=anc[x][i];
}
}
if(x==y) return x;
for(int i=log2n;i>=0;i--){
if(anc[x][i]!=anc[y][i]){
x=anc[x][i];
y=anc[y][i];
}
}
return anc[x][0];
}
int get_dist(int u,int v){
return deep[u]+deep[v]-2*deep[lca(u,v)];
}
int get_son_kth(int x,int k){//查询子树中第k大
return CT.query(CT.root[dfnl[x]-1],CT.root[dfnr[x]],k,1,n);
}
void build(){
log2n=log2(n)+1;
dfs(1,0);
for(int i=1;i<=n;i++) CT.update(CT.root[i],CT.root[i-1],hash_dfn[i],1,n);
}
}
namespace real_tree{
struct edge{
int from;
int to;
int len;
int next;
}E[maxn*2+5];
int esz=1;
int head[maxn+5];
void add_edge(int u,int v,int w){
esz++;
E[esz].from=u;
E[esz].to=v;
E[esz].next=head[u];
E[esz].len=w;
head[u]=esz;
esz++;
E[esz].from=v;
E[esz].to=u;
E[esz].next=head[v];
E[esz].len=w;
head[v]=esz;
}
int log2n;
int deep[maxn+5];
ll dist[maxn+5];
int anc[maxn+5][maxlogn+5];
ll dsum[maxn+5][maxlogn+5];//x向上走2^i辈祖先的距离
void dfs(int x,int fa){
deep[x]=deep[fa]+1;
anc[x][0]=fa;
for(int i=1;i<=log2n;i++){
anc[x][i]=anc[anc[x][i-1]][i-1];
dsum[x][i]=dsum[x][i-1]+dsum[anc[x][i-1]][i-1];
}
for(int i=head[x];i;i=E[i].next){
int y=E[i].to;
if(y!=fa){
dist[y]=dist[x]+E[i].len;
dsum[y][0]=E[i].len;
dfs(y,x);
}
}
}
struct oper{
int x;
ll to;
}op[maxn+5];
ll idl[maxn+5],idr[maxn+5];//这个大节点包含小节点的编号范围
int from[maxn+5];//大节点来自模板树上的节点编号
ll link[maxn+5];//记录一下大节点x接到了哪里
int sz;
int get_root(ll x){//找到包含小节点的大节点
//二分出包含小节点的区间
int l=1,r=sz;
int mid;
int ans=0;
while(l<=r){
mid=(l+r)>>1;
if(idr[mid]>=x){
ans=mid;
r=mid-1;
}else l=mid+1;
}
return ans;
}
int get_tpid(ll x){//找到小节点x在模板树里对应的节点
//实际上就是找模板树子树from[rt]里第x-idl[rt]+1小的节点,用主席树做
int rt=get_root(x);
return template_tree::get_son_kth(from[rt],x-idl[rt]+1);
}
void build(){
sz=1;
ll cnt=n;
from[1]=1;
idl[1]=1;
idr[1]=n;
for(int i=1;i<=m;i++){
sz++;
int rt=get_root(op[i].to);
int len=template_tree::get_dist(from[rt],get_tpid(op[i].to))+1;
add_edge(sz,rt,len);
link[sz]=op[i].to;
from[sz]=op[i].x;
idl[sz]=cnt+1;
idr[sz]=cnt+template_tree::dfnr[op[i].x]-template_tree::dfnl[op[i].x]+1;
cnt=idr[sz];
}
log2n=log2(sz)+1;
dfs(1,0);
}
ll get_dist(ll x,ll y){
int rtx=get_root(x);
int rty=get_root(y);
ll ans=0;
if(rtx==rty){
return template_tree::get_dist(get_tpid(x),get_tpid(y));
}else{
if(deep[rtx]<deep[rty]){
swap(x,y);
swap(rtx,rty);
}
ans+=template_tree::get_dist(get_tpid(x),from[rtx]);//x到rtx的距离
x=rtx;
for(int i=log2n;i>=0;i--){
if(deep[anc[x][i]]>deep[rty]){//差一步不要跳
ans+=dsum[x][i];
x=anc[x][i];
}
}
if(anc[x][0]==rty) return ans+1+template_tree::get_dist(get_tpid(link[x]),get_tpid(y));
//如果是一条链,需要特判,这里不能在大树上跳一步到rty,因为经过rty不是最短路径,
//如果x到rty有一条边,即x对应的子树的根是接在rty中的某个小节点的子树上的
//应从x对应子树的根,跳长度为1到link[x],再从link[x]到y
ans+=template_tree::get_dist(get_tpid(y),from[rty]);//y到rty的距离
y=rty;
if(deep[x]>deep[rty]){
ans+=dsum[x][0];
x=anc[x][0];
}//如果不是一条链,且深度不等,才跳到同一深度
for(int i=log2n;i>=0;i--){
if(anc[x][i]!=anc[y][i]){
ans+=dsum[x][i];
ans+=dsum[y][i];
x=anc[x][i];
y=anc[y][i];
}
}
//最后一步
x=link[x];
y=link[y];
ans+=2;
ans+=template_tree::get_dist(get_tpid(x),get_tpid(y));
return ans;
}
}
}
int main(){
int u,v;
ll xx,yy;
scanf("%d %d %d",&n,&m,&q);
for(int i=1;i<n;i++){
scanf("%d %d",&u,&v);
template_tree::add_edge(u,v);
}
template_tree::build();
for(int i=1;i<=m;i++){
scanf("%d %lld",&real_tree::op[i].x,&real_tree::op[i].to);
}
real_tree::build();
for(int i=1;i<=q;i++){
scanf("%lld %lld",&xx,&yy);
printf("%lld\n",real_tree::get_dist(xx,yy));
}
}
数据生成器
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<ctime>
#define maxn 5
#define maxm 3
#define maxq 5
#define maxv 1000
using namespace std;
typedef long long ll;
ll random(ll n){
return (ll)rand()*rand()%n+1;
}
ll random(ll l,ll r){
return (ll)rand()*rand()%(r-l+1)+l;
}
int n,m,q;
struct edge{
int from;
int to;
int next;
}E[maxn*2+5];
int esz=1;
int head[maxn+5];
void add_edge(int u,int v){
esz++;
E[esz].from=u;
E[esz].to=v;
E[esz].next=head[u];
head[u]=esz;
}
int sz[maxn+5];
void dfs(int x,int fa){
sz[x]=1;
for(int i=head[x];i;i=E[i].next){
int y=E[i].to;
if(y!=fa){
dfs(y,x);
sz[x]+=sz[y];
}
}
}
ll newn;
int main(){
srand(time(NULL));
n=random(2,maxn);
m=random(maxm);
q=random(maxq);
printf("%d %d %d\n",n,m,q);
for(int i=2;i<=n;i++){
int u=random(i-1);
add_edge(u,i);
add_edge(i,u);
printf("%d %d\n",i,u);
}
dfs(1,0);
newn=n;
for(int i=1;i<=m;i++){
int x=random(n);
printf("%d %lld\n",x,random(newn));
newn+=sz[x];
}
for(int i=1;i<=q;i++){
printf("%lld %lld\n",random(newn),random(newn));
}
}
[HNOI2016]树(可持久化线段树+树上倍增)的更多相关文章
- [BZOJ 4771]七彩树(可持久化线段树+树上差分)
[BZOJ 4771]七彩树(可持久化线段树+树上差分) 题面 给定一棵n个点的有根树,编号依次为1到n,其中1号点是根节点.每个节点都被染上了某一种颜色,其中第i个节点的颜色为c[i].如果c[i] ...
- 主席树||可持久化线段树+离散化 || 莫队+分块 ||BZOJ 3585: mex || Luogu P4137 Rmq Problem / mex
题面:Rmq Problem / mex 题解: 先离散化,然后插一堆空白,大体就是如果(对于以a.data<b.data排序后的A)A[i-1].data+1!=A[i].data,则插一个空 ...
- [TS-A1505] [清橙2013中国国家集训队第二次作业] 树 [可持久化线段树,求树上路径第k大]
按Dfs序逐个插入点,建立可持久化线段树,每次查询即可,具体详见代码. 不知道为什么,代码慢的要死,, #include <iostream> #include <algorithm ...
- BZOJ4771七彩树——可持久化线段树+set+树链的并+LCA
给定一棵n个点的有根树,编号依次为1到n,其中1号点是根节点.每个节点都被染上了某一种颜色,其中第i个节 点的颜色为c[i].如果c[i]=c[j],那么我们认为点i和点j拥有相同的颜色.定义dept ...
- 权值线段树&&可持久化线段树&&主席树
权值线段树 顾名思义,就是以权值为下标建立的线段树. 现在让我们来考虑考虑上面那句话的产生的三个小问题: 1. 如果说权值作为下标了,那这颗线段树里存什么呢? ----- 这颗线段树中, 记录每个值出 ...
- 归并树 划分树 可持久化线段树(主席树) 入门题 hdu 2665
如果题目给出1e5的数据范围,,以前只会用n*log(n)的方法去想 今天学了一下两三种n*n*log(n)的数据结构 他们就是大名鼎鼎的 归并树 划分树 主席树,,,, 首先来说两个问题,,区间第k ...
- 主席树[可持久化线段树](hdu 2665 Kth number、SP 10628 Count on a tree、ZOJ 2112 Dynamic Rankings、codeforces 813E Army Creation、codeforces960F:Pathwalks )
在今天三黑(恶意评分刷上去的那种)两紫的智推中,突然出现了P3834 [模板]可持久化线段树 1(主席树)就突然有了不详的预感2333 果然...然后我gg了!被大佬虐了! hdu 2665 Kth ...
- BZOJ.4771.七彩树(可持久化线段树)
BZOJ 考虑没有深度限制,对整棵子树询问怎么做. 对于同种颜色中DFS序相邻的两个点\(u,v\),在\(dfn[u],dfn[v]\)处分别\(+1\),\(dfn[LCA(u,v)]\)处\(- ...
- BZOJ 3483 SGU505 Prefixes and suffixes(字典树+可持久化线段树)
[题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=3483 [题目大意] 给出一些串,同时给出m对前缀后缀,询问有多少串满足给出的前缀后缀模 ...
- BZOJ 3439 Kpm的MCpassword Trie树+可持久化线段树
题目大意:给定n个字符串,对于每一个字符串求以这个字符串为后缀的字符串中第k小的编号 首先将字符串反转 那么就变成了对于每一个字符串求以这个字符串为前缀的字符串中第k小的编号 然后考虑对字符串排序 那 ...
随机推荐
- httprunner
https://cn.httprunner.org/quickstart/ httprunner官方 https://testerhome.com/opensource_projects/httpru ...
- java实现队列和栈
队列:队列其实就是我们生活中的排队现象,先进入的先出,后进入的后出,代码实现如下: public class Queue<E> { private int front;//队头一端,只允许 ...
- js中或者vue中 Object.assign()用法详解
Object.assign()是浅拷贝. 合并对象 var o1 = { a: 1 }; var o2 = { b: 2 }; var o3 = { c: 3 }; var obj = Object. ...
- SonarQube规则之坏味道类型
1.Abbreviation As Word In Name (默认 关闭)坏味道 主要检查验证标识符名称中的缩写(连续大写字母)长度,还允许执行骆驼案例命名allowedAbbreviationLe ...
- React Native 之FlatList
1.新建项目 2.因为要用到导航跳转, 所以添加依赖,,这里拷贝这个: "dependencies": { "@types/react": "^16. ...
- JDK,JRE与JVM浅析
JAVA的两个特性: 1, 开源-指的是源代码免费 2,跨平台(可移植性好) 跨平台:是指跨操作系统 JVM(java virtual machine,java虚拟机) JVM就像是两国谈判时的使者充 ...
- Linux shell - 重命名文件和文件夹(mv)
linux下重命名文件或文件夹的命令mv既可以重命名,又可以移动文件或文件夹. 例子:将目录A重命名为B mv A B 例子:将/a目录移动到/b下,并重命名为c mv /a /b/c
- postgresql获取表最后更新时间(通过发布订阅机制将消息发送给应用程序)
一.创建测试表 CREATE TABLE weather( city ), temp_lo int, --最低温度 temp_hi int, --最高温度 prcp real, --湿度 date d ...
- ORACLE DG在线日志修改
ORACLE DG在线日志修改 SQL>select SEQUENCE#,first_time,next_time,APPLIED, THREAD# from v$archived_log or ...
- tar 打包文件
tar支持通配符, 可以用* ?等来指定多个文件 在指明压缩文件名的时候, 一定要带上 -f选项 压缩文件名中间 最好不要带特殊符号, 如& ? * +等, shell bash 会作一些特殊 ...