[LOJ 2092][BZOJ 4573][UOJ 195][ZJOI 2016]大♂森林

题意

给定一个树序列, 初始时所有树都只有一个点, 要求支持三种操作:

  1. 区间种树(在某个特定点上长出一个子结点)
  2. 区间更改种树点(就是改上面那个操作中的「特定点」)
  3. 查询某棵树上两个点间的距离

\(n\le 1\times 10^5, q\le 2\times 10^5\). 不强制在线. 长出来的点标号一致, 与种树操作的顺序一致. 保证2操作合法.

题解

ZJOI都是神仙题啊QAQ...

首先这题序列上的元素是树...一棵树得占它个 \(O(n)\) 级别的空间所以肯定得想办法离线之后一个一个处理...

其次我们发现好像对于已经长出来的点, 后面再怎么修改也不会和它有什么关系了. 于是我们可以把重点放在前两个操作而把 2 操作丢到最后等树形态构造完成之后再搞.

还有一点就是其实多长出来的点并不会影响答案(因为保证 2 操作合法, 而不合法的点一定不会是合法的点的祖先), 所以其实可以考虑维护一整棵包含所有 0 操作生长出来的结点的树. 这样我们就可以避免一直添点删点的问题了.

也就是说我们现在只剩下最辣手的操作 1 了.

考虑从第 \(i\) 棵树的形态如何快速转化为第 \(i+1\) 棵的形态.

如果这两棵树形态不同, 必然是一棵执行了某个操作 1 而另一个没有. 假设原生长点是 \(u\), 新生长点是 \(v\), 则在操作 1 之后, 原来长在 \(u\) 下面的所有点实际上都应该挂在 \(v\) 下面才对. 所以实际上就是一个子树移动的过程.

子树移动? Cut一下再Link一下就完了?

假的...

现在的需求是将某个点下面的所有子树都移动走, 但是它有多少子树可不一定...

我们可以放一个虚点来把它们收束起来. 预处理的时候对每个 1 操作建立一个虚点, 距离这个操作最近的挂上去的点都挂在这个虚点上. 这样如果这个 1 操作产生了子树移动, 直接把虚点切下来就好了.

什么你说虚点会导致距离增加? 裆燃是把虚点的 size 设为 0 辣~

一些小的注意事项

  1. 注意LCT查询LCA的正确姿势以及不同Access写法的不同副作用. (\(0\text{pts}\rightarrow40\text{pts}\))
  2. 注意每个实点都有一个存在区间, 如果要把某个子树挂到 \(v\) 上需要检查 \(v\) 在哪一段中存在. 不存在的不能理会. (\(40\text{pts}\rightarrow70\text{pts}\))
  3. 注意 1 号点的存在区间要初始化为 \([1,n]\). (\(70\text{pts}\rightarrow100\text{pts}\))

参考代码

#include <bits/stdc++.h>
#define _O0 __attribute__((optimize("O0"))) // 防止沙雕编译器把this当成非空来优化 const int MAXN=3e5+10; struct Query{
int type;
int time;
int u;
int v;
int r;
bool friend operator<(const Query& a,const Query& b){
return std::make_pair(a.type,a.time)<std::make_pair(b.type,b.time);
}
}; struct Dump{
int t;
int a;
int b;
int c;
}; #define lch chd[0]
#define rch chd[1]
#define kch chd[k]
#define xch chd[k^1] struct Node{
int v;
int sz;
bool rev;
Node* prt;
Node* pprt;
Node* chd[2];
Node(int v):v(v),sz(v),rev(false),prt(NULL),pprt(NULL),chd{NULL,NULL}{}
inline _O0 int size(){
return this==NULL?0:this->sz;
}
inline void Maintain(){
this->sz=this->lch->size()+this->rch->size()+this->v;
}
inline _O0 void Flip(){
if(this!=NULL){
std::swap(this->lch,this->rch);
this->rev=!this->rev;
}
}
inline void PushDown(){
if(this->rev){
this->lch->Flip();
this->rch->Flip();
this->rev=false;
}
}
};
Node* N[MAXN]; void Rotate(Node* root,int k){
Node* tmp=root->xch;
root->PushDown();
tmp->PushDown();
tmp->prt=root->prt;
if(root->prt==NULL){
tmp->pprt=root->pprt;
root->pprt=NULL;
}
else if(root->prt->lch==root)
root->prt->lch=tmp;
else
root->prt->rch=tmp;
root->xch=tmp->kch;
if(root->xch!=NULL)
root->xch->prt=root;
tmp->kch=root;
root->prt=tmp;
root->Maintain();
tmp->Maintain();
} void Splay(Node* root){
while(root->prt!=NULL){
int k=root->prt->lch==root;
if(root->prt->prt==NULL)
Rotate(root->prt,k);
else{
int d=root->prt->prt->lch==root->prt;
Rotate(k==d?root->prt->prt:root->prt,k);
Rotate(root->prt,d);
}
}
} void Expose(Node* root){
Splay(root);
root->PushDown();
if(root->rch){
root->rch->pprt=root;
root->rch->prt=NULL;
root->rch=NULL;
root->Maintain();
}
} Node* Access(Node* root){
Node* dump=root;
Node* tmp=NULL;
while(root){
Expose(root);
root->rch=tmp;
if(tmp){
tmp->pprt=NULL;
tmp->prt=root;
}
root->Maintain();
tmp=root;
root=root->pprt;
}
Splay(dump);
return tmp;
} void Evert(Node* root){
Access(root);
root->Flip();
} Node* FindRoot(Node* root){
Access(root);
Node* ans=root;
while(ans->lch)
ans=ans->lch;
Splay(ans);
return ans;
} int Calc(int a,int b){
if(FindRoot(N[a])!=FindRoot(N[b]))
return -1;
int ans=0;
Access(N[a]);
ans+=N[a]->size();
Node* lca=Access(N[b]);
ans+=N[b]->size();
// printf("%d\n",ans);
Access(lca);
ans-=2*lca->size();
// printf("%d %d lca=%p\n",a,b,lca);
return ans;
} void Link(int x,int y){
// printf("link %d <- %d\n",x,y);
Evert(N[y]);
N[y]->pprt=N[x];
} void Cut(int x){
// printf("cut %d\n",x);
Evert(N[1]);
Access(N[x]);
N[x]->PushDown();
N[x]->lch->prt=NULL;
N[x]->lch=NULL;
N[x]->Maintain();
} int n;
int q;
int cnt;
int L[MAXN];
int R[MAXN];
Dump D[MAXN];
int ans[MAXN];
int prt[MAXN];
int alive[MAXN];
std::vector<Query> Q[MAXN]; int main(){
scanf("%d%d",&n,&q);
cnt=1;
N[0]=new Node(0);
N[1]=new Node(1);
Link(1,0);
L[1]=1,R[1]=n;
prt[0]=1;
prt[1]=-1;
for(int i=0;i<q;i++){
scanf("%d%d%d",&D[i].t,&D[i].a,&D[i].b);
if(D[i].t!=0)
scanf("%d",&D[i].c);
else{
N[++cnt]=new Node(1);
L[cnt]=D[i].a;
R[cnt]=D[i].b;
}
}
int last=0;
int cur=1;
for(int i=0;i<q;i++){
if(D[i].t==0){
prt[++cur]=last;
Link(last,cur);
}
else if(D[i].t==1){
int l=std::max(L[D[i].c],D[i].a),r=std::min(R[D[i].c],D[i].b);
if(l>r)
continue;
N[++cnt]=new Node(0);
Link(last,cnt);
prt[cnt]=last;
last=cnt;
Q[l].push_back({1,i,cnt,D[i].c,r+1});
}
else
Q[D[i].a].push_back({2,i,D[i].b,D[i].c,0});
}
// for(int i=0;i<=cnt;i++)
// printf("N[%d]=%p\n",i,N[i]);
for(int i=1;i<=n;i++){
std::sort(Q[i].begin(),Q[i].end());
for(auto q:Q[i]){
if(q.type==1){
if(q.r)
Q[q.r].push_back({1,q.time,q.u,prt[q.u],0});
Cut(q.u);
Link(q.v,q.u);
prt[q.u]=q.v;
}
else{
// assert(q.type==2);
ans[q.time]=Calc(q.u,q.v);
}
}
}
for(int i=0;i<q;i++)
if(D[i].t==2)
printf("%d\n",ans[i]);
return 0;
}

[BZOJ 4573][ZJOI 2016]大森林的更多相关文章

  1. [BZOJ 4455] [ZJOI 2016] 小星星 (树形dp+容斥原理+状态压缩)

    [BZOJ 4455] [ZJOI 2016] 小星星 (树形dp+容斥原理+状态压缩) 题面 给出一棵树和一个图,点数均为n,问有多少种方法把树的节点标号,使得对于树上的任意两个节点u,v,若树上u ...

  2. bzoj 4573 大森林

    bzoj 4573 大森林 由于树上路径是唯一的,查询合法的两个点间路径长度显然与其他加点操作无关,所以可以离线处理,将所有的查询放在加点后. 这样我们可以对每棵树都在上颗树的基础上处理好形态后,处理 ...

  3. 【刷题】BZOJ 4573 [Zjoi2016]大森林

    Description 小Y家里有一个大森林,里面有n棵树,编号从1到n.一开始这些树都只是树苗,只有一个节点,标号为1.这些树都有一个特殊的节点,我们称之为生长节点,这些节点有生长出子节点的能力.小 ...

  4. bzoj 4573: [Zjoi2016]大森林

    Description 小Y家里有一个大森林,里面有n棵树,编号从1到n.一开始这些树都只是树苗,只有一个节点,标号为1.这些树 都有一个特殊的节点,我们称之为生长节点,这些节点有生长出子节点的能力. ...

  5. BZOJ4573:[ZJOI2016]大森林——题解

    http://www.lydsy.com/JudgeOnline/problem.php?id=4573 https://www.luogu.org/problemnew/show/P3348#sub ...

  6. @loj - 2092@ 「ZJOI2016」大森林

    目录 @description@ @solution@ @accepted code@ @details@ @description@ 小 Y 家里有一个大森林,里面有 n 棵树,编号从 1 到 n. ...

  7. [BZOJ 1412][ZJOI 2009] 狼和羊的故事

    题目大意 有一个 (n times m) 的网格,每一个格子上是羊.狼.空地中的一种,羊和狼可以走上空地.现要在格子边上建立围栏,求把狼羊分离的最少围栏数. (1 leqslant n, ; m le ...

  8. 「ZJOI2016」大森林 解题报告

    「ZJOI2016」大森林 神仙题... 很显然线段树搞不了 考虑离线操作 我们只搞一颗树,从位置1一直往后移动,然后维护它的形态试试 显然操作0,1都可以拆成差分的形式,就是加入和删除 因为保证了操 ...

  9. [ZJOI2016]大森林(LCT)

    题目描述 小Y家里有一个大森林,里面有n棵树,编号从1到n.一开始这些树都只是树苗,只有一个节点,标号为1.这些树都有一个特殊的节点,我们称之为生长节点,这些节点有生长出子节点的能力. 小Y掌握了一种 ...

随机推荐

  1. [PY3]——字符串的分割、匹配、搜索方法总结

    ?分割.匹配.搜索时可以用到什么样的解决方法? 分割方法总结 1. str.split( ) * 分割字符串 * 返回列表 s1='I love python' # 默认以空格为界定符,且多个空格都当 ...

  2. JAVA练手--链表

    package tet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; impo ...

  3. 用INFORMATION_SCHEMA逻辑MySQL的索引

    分库分表的场景下,变更目前还不知道有哪个表变更索引失败,是不是所有的表都变更成功了,所以可以从INFORMATION_SCHEMA通过罗列索引个数,或者查看索引行,就可以知道是不是所有的都变更成功了: ...

  4. 八: 操作提示(wxml 即将废弃)

    首先需要注意的是 wxml的这些属性将要被废弃,不过可以看两眼.不愿意看的可以看下一章节同样是操作回馈只不过是js版的哦.   一.action-sheet 操作菜单 从屏幕底下出来菜单. 这里不用w ...

  5. ComBox、listBox、checklistBox控件

    omBox控件被称为下拉组合框控件,是由System.windows.Forms.ComBox类提供的,主要作用是讲一个集合数据以组合框的形式显示给用户,当用户单击时将以下拉框显示给用户,供用户选择一 ...

  6. web弹出对话框

    Page.ClientScript.RegisterStartupScript(this.GetType(), "", "<script>alert('请输入 ...

  7. 怎么用PHP发送HTTP请求(POST请求、GET请求)?

    file_get_contents版本: 01 /** 02 * 发送post请求 03 * @param string $url 请求地址 04 * @param array $post_data ...

  8. 一:Bootstrap-css样式

    页面大块布局: div.container 栅格系统: 一行分成 12 列 div.row div.col-md-12 div.col-xs-12 <div class="row&qu ...

  9. 11、springboot之包扫描

    如上图,将Application启动类放入hello.aaa文件夹下面 启动springboot,访问http://localhost:9999/testJson,报404错误,在启动类上面加上@Co ...

  10. 最短路问题(dijkstral 算法)(优化待续)

    迪杰斯特拉算法是由荷兰计算机科学家狄克斯特拉于1959 年提出的,因此又叫狄克斯特拉算法.是从一个顶点到其余各顶点的最短路径算法,解决的是有向图中最短路径问题.迪杰斯特拉算法主要特点是以起始点为中心向 ...