【HNOI2016】树

题目描述

每一个复制过来的子树(我们称为一个树团)有用的只有需要被访问的节点,包括根,根的父亲,要询问的点。我们只需要求出这些点到其所在树团根的距离以及倍增数组就好了。

需要讨论一些不同的情况。

然而我头铁,写了虚树,时间/空间常数大到自闭(不敢乱写虚树了)。

要注意的细节是,我维护了两个数组:\(dep_v\)表示\(v\)到根的节点数;\(dis_v\)表示\(v\)到根的路径数。这两个数组不能混用。

代码:

#include<bits/stdc++.h>
#define ll long long
#define N 100005 using namespace std;
inline ll Get() {ll x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}while('0'<=ch&&ch<='9') {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}return x*f;} int n,m,Q;
struct road {int to,next;}s[N<<1]; int h[N],cnt;
void add(int i,int j) {s[++cnt]=(road) {j,h[i]};h[i]=cnt;}
int lx,rx;
int rt[N],sum[N*18];
int ls[N*18],rs[N*18];
int tot; void Insert(int &v,int old,int lx,int rx,int p) {
v=++tot;
sum[v]=sum[old]+1;
ls[v]=ls[old];
rs[v]=rs[old];
if(lx==rx) return ;
int mid=lx+rx>>1;
if(p<=mid) Insert(ls[v],ls[old],lx,mid,p);
else Insert(rs[v],rs[old],mid+1,rx,p);
} int query_sum(int a,int b,int lx,int rx,int lim) {
if(lx>lim) return 0;
if(rx<=lim) return sum[b]-sum[a];
int mid=lx+rx>>1;
return query_sum(ls[a],ls[b],lx,mid,lim)+query_sum(rs[a],rs[b],mid+1,rx,lim);
} int Find_kth(int a,int b,int k,int lx,int rx) {
if(lx==rx) return lx;
int mid=lx+rx>>1;
if(k<=sum[ls[b]]-sum[ls[a]]) return Find_kth(ls[a],ls[b],k,lx,mid);
else return Find_kth(rs[a],rs[b],k-sum[ls[b]]+sum[ls[a]],mid+1,rx);
} int dep[int(N*6.7)],size[N];
ll dis[int(N*6.7)];
int dfn[N],edf[N],dfn_id;
int lst[N];
struct Integer {
typedef unsigned char byte;
byte a,b,c;
operator int(){return int(a)<<16|int(b)<<8|int(c);}
Integer(int val=0){
a=val>>16;
b=(val>>8)&0xff;
c=val&0xff;
}
Integer operator + (Integer b){return Integer(int(*this)+int(b));}
Integer operator - (Integer b){return Integer(int(*this)-int(b));}
Integer operator * (Integer b){return Integer(int(*this)*int(b));}
Integer operator / (Integer b){return Integer(int(*this)/int(b));}
Integer operator = (Integer b){
this->a=b.a;
this->b=b.b;
this->c=b.c;
return *this;
} };
Integer fa[int(N*6.7)][19]; void dfs(int v) {
dfn[v]=++dfn_id;
lst[dfn_id]=v;
for(int i=1;i<19;i++) fa[v][i]=fa[fa[v][i-1]][i-1];
size[v]=1;
for(int i=h[v];i;i=s[i].next) {
int to=s[i].to;
if(to==fa[v][0]) continue ;
fa[to][0]=v;
dep[to]=dep[v]+1;
dis[to]=dis[v]+1;
dfs(to);
size[v]+=size[to];
}
edf[v]=dfn_id;
} int lca(int a,int b) {
if(dep[a]<dep[b]) swap(a,b);
for(int i=18;i>=0;i--)
if(fa[a][i]&&dep[fa[a][i]]>=dep[b])
a=fa[a][i];
if(a==b) return a;
for(int i=18;i>=0;i--)
if(fa[a][i]!=fa[b][i])
a=fa[a][i],b=fa[b][i];
return fa[a][0];
} struct plant {ll a,b;}p[N];
struct query {ll a,b;}q[N]; ll key[N<<2];
int id_tot;
bool cmp(int a,int b) {return dfn[a]<dfn[b];}
map<ll,int>id;
vector<ll>tem;
ll pos[N]; int FLAG=1; void build(ll d,int rt,ll FA) {
static vector<ll>a;
static ll st[N],top;
a.resize(tem.size());
for(int i=0;i<tem.size();i++) {
int x=Find_kth(::rt[dfn[rt]-1],::rt[edf[rt]],tem[i]-d,lx,rx);
a[i]=x;
}
sort(a.begin(),a.end(),cmp);
for(int i=0,x=a.size()-1;i<x;i++) {
a.push_back(lca(a[i],a[i+1]));
}
sort(a.begin(),a.end(),cmp);
int cc=unique(a.begin(),a.end())-a.begin();
for(int i=0;i<cc;i++) {
pos[a[i]]=d+query_sum(::rt[dfn[rt]-1],::rt[edf[rt]],lx,rx,a[i]);
}
for(int i=0;i<cc;i++) {
if(id.find(pos[a[i]])==id.end()) id[pos[a[i]]]=++id_tot;
}
for(int i=0;i<cc;i++) pos[a[i]]=id[pos[a[i]]];
fa[pos[a[0]]][0]=FA;
dep[pos[a[0]]]=dep[FA]+1;
dis[pos[a[0]]]=dis[FA]+1;
st[top=1]=a[0];
for(int i=1;i<cc;i++) {
while(edf[st[top]]<dfn[a[i]]) top--;
dep[pos[a[i]]]=dep[pos[st[top]]]+1;
dis[pos[a[i]]]=dis[pos[st[top]]]+dis[a[i]]-dis[st[top]];
fa[pos[a[i]]][0]=pos[st[top]];
st[++top]=a[i];
}
for(int i=0;i<cc;i++) {
int now=pos[a[i]];
for(int j=1;j<19;j++) fa[now][j]=fa[fa[now][j-1]][j-1];
}
}
int main() {
n=Get(),m=Get(),Q=Get();
lx=1,rx=n;
int a,b;
for(int i=1;i<n;i++) {
a=Get(),b=Get();
add(a,b),add(b,a);
} dfs(1);
for(int i=1;i<=n;i++) {
rt[i]=rt[i-1];
Insert(rt[i],rt[i],lx,rx,lst[i]);
}
for(int i=1;i<=n;i++) id[i]=i;
ll SUM=n; for(int i=1;i<=m;i++) {
p[i].a=Get(),p[i].b=Get();
key[++key[0]]=SUM+query_sum(rt[dfn[p[i].a]-1],rt[edf[p[i].a]],lx,rx,p[i].a);
if(p[i].b>n) key[++key[0]]=(p[i].b);
SUM+=size[p[i].a];
} for(int i=1;i<=Q;i++) {
q[i].a=Get(),q[i].b=Get();
if(q[i].a>n) key[++key[0]]=(q[i].a);
if(q[i].b>n) key[++key[0]]=(q[i].b);
} sort(key+1,key+1+key[0]); id_tot=n;
int cc=unique(key+1,key+1+key[0])-key-1; int tag=1;
SUM=n;
for(int i=1;i<=m;i++) {
tem.clear();
ll pre=SUM;
SUM+=size[p[i].a];
while(tag<=cc&&key[tag]<=SUM) tem.push_back(key[tag]),tag++;
build(pre,p[i].a,id[p[i].b]);
}
for(int i=1;i<=Q;i++) {
int a=id[q[i].a],b=id[q[i].b];
int f=lca(a,b);
cout<<dis[a]+dis[b]-2*dis[f]<<"\n";
}
return 0;
}

【HNOI2016】树的更多相关文章

  1. BZOJ 4539: [Hnoi2016]树 [主席树 lca]

    4539: [Hnoi2016]树 题意:不想写.复制模板树的子树,查询两点间距离. *** 终于有一道会做的题了...... 画一画发现可以把每次复制的子树看成一个大点来建一棵树,两点的lca一定在 ...

  2. 【LG3248】[HNOI2016]树

    [LG3248][HNOI2016]树 题面 洛谷 题解 因为每次你加入的点是原树上某一棵子树 那么我们一次加入一个点,代表一棵子树加到大树下面 那么我们要找到一个点在一个大点中用主席树在\(dfs\ ...

  3. 4539: [Hnoi2016]树

    4539: [Hnoi2016]树 链接 分析: 主席树+倍增. 代码: #include<cstdio> #include<algorithm> #include<cs ...

  4. [BZOJ4539][HNOI2016]树(主席树)

    4539: [Hnoi2016]树 Time Limit: 40 Sec  Memory Limit: 256 MBSubmit: 746  Solved: 292[Submit][Status][D ...

  5. [HNOI2016]树(可持久化线段树+树上倍增)

    [HNOI2016]树(可持久化线段树+树上倍增) 题面 给出一棵n个点的模板树和大树,根为1,初始的时候大树和模板树相同.接下来操作m次,每次从模板树里取出一棵子树,把它作为新树里节点y的儿子.操作 ...

  6. BZOJ4539: [Hnoi2016]树

    复制的树缩点,主席树查k小,毫无技术含量,纯码农题. #include<bits/stdc++.h> #define u first #define v second #define F ...

  7. bzoj 4539: [Hnoi2016]树

    Description 小A想做一棵很大的树,但是他手上的材料有限,只好用点小技巧了.开始,小A只有一棵结点数为N的树,结 点的编号为1,2,-,N,其中结点1为根:我们称这颗树为模板树.小A决定通过 ...

  8. [HNOI2016]树

    Description 小A想做一棵很大的树,但是他手上的材料有限,只好用点小技巧了.开始,小A只有一棵结点数为N的树,结 点的编号为1,2,…,N,其中结点1为根:我们称这颗树为模板树.小A决定通过 ...

  9. 2019.03.25 bzoj4539: [Hnoi2016]树(主席树+倍增)

    传送门 题意:给一棵大树,令一棵模板树与这棵树相同,然后进行mmm次操作,每次选择模板树中的一个节点aaa和大树中一个节点bbb,把aaa这棵子树接在bbb上面,节点编号顺序跟aaa中的编号顺序相同. ...

  10. 洛谷P3248 [HNOI2016]树(主席树 倍增 )

    题意 题目链接 Sol 从上午九点淦到现在qwq 思路比较简单,就是把每次加入的一坨点看成一个,然后直接倍增搞.. 然后慢慢调就可以了... 最后数量级会到达\(10^{10}\),所以应该开long ...

随机推荐

  1. [PHP]算法-堆排序的PHP实现

    1.堆(二叉堆):可以视为一棵完全的二叉树,除了最底层之外,每一层都是满的,这使得堆可以利用数组来表示,每一个结点对应数组中的一个元素 2.给出某个结点的下标,可以计算出父结点的和孩子结点的下标; p ...

  2. SpringBoot的打包失败

    打包是根据pom.xml文件来打的包. 如果使用Maven的默认打包,只会将src/main下的java和resources里面的内容打进Jar包中. 通过maven-assembly-plugin这 ...

  3. 如何用STAR法则来回答「宝洁八大问」

    掌握宝洁八大问,其实就是掌握了半个求职季 每年高峰期,很多同学会问到关于宝洁八大的问题,如何准备.怎么讲故事.如何体现自己的特点等等.针对同学们的提问,分享一篇关于如何回答好宝洁八大问的文章,希望能够 ...

  4. SSH整合jar包分享及登陆实例详解

    相关jar包分享:struts2+hibernate3+spring3 以及aop ,mysql,以及整合必须包. 链接:https://pan.baidu.com/s/1nCHmSsKU0hiV8D ...

  5. SpringBoot设置文件上传大小限制--默认为1M

    SpringBoot默认上传文件大小不能超过1MB,超过之后会报以下异常:org.apache.tomcat.util.http.fileupload.FileUploadBase$FileSizeL ...

  6. springboot Redis 缓存

    1,先整合 redis 和 mybatis 步骤一: springboot 整合 redis 步骤二: springboot 整合 mybatis 2,启动类添加 @EnableCaching 注解, ...

  7. leetcode-9.回文数(水仙花数)

    leetcode-9.回文数(水仙花数) 题意:给定整数,判断是否是水仙花数(回文数),返回判断结果 算法: 1.判断负数, 如果是负数直接返回false 2.将整数逐位拆解,用数组存储 3.遍历数组 ...

  8. SpringBoot集成Swagger接口管理工具

    手写Api文档的几个痛点: 文档需要更新的时候,需要再次发送一份给前端,也就是文档更新交流不及时. 接口返回结果不明确 不能直接在线测试接口,通常需要使用工具,比如postman 接口文档太多,不好管 ...

  9. 测者的性测试手册:SWAP的监控

    swap是什么 swap是磁盘上的一块区域,可以使一个磁盘分区,也可以是一个文件,也可能是一个两种的组合.当物理内存资源紧张的时候,操作系统(Linux)会将一些不常访问的数据放到swap里.为其他常 ...

  10. Jupyter Notebook默认工作路径的修改

    相信每一个学习Python的童鞋,都尝试过Jupyter Notebook,所以我也就不多介绍,真的还不错哎这软件. 不过美中不足的,就是它的默认工作路径,每次打开都是系统盘的Administrato ...