[BZOJ 4573][ZJOI 2016]大森林
[LOJ 2092][BZOJ 4573][UOJ 195][ZJOI 2016]大♂森林
题意
给定一个树序列, 初始时所有树都只有一个点, 要求支持三种操作:
- 区间种树(在某个特定点上长出一个子结点)
 - 区间更改种树点(就是改上面那个操作中的「特定点」)
 - 查询某棵树上两个点间的距离
 
\(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 辣~
一些小的注意事项
- 注意LCT查询LCA的正确姿势以及不同Access写法的不同副作用. (\(0\text{pts}\rightarrow40\text{pts}\))
 - 注意每个实点都有一个存在区间, 如果要把某个子树挂到 \(v\) 上需要检查 \(v\) 在哪一段中存在. 不存在的不能理会. (\(40\text{pts}\rightarrow70\text{pts}\))
 - 注意 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]大森林的更多相关文章
- [BZOJ 4455] [ZJOI 2016] 小星星 (树形dp+容斥原理+状态压缩)
		
[BZOJ 4455] [ZJOI 2016] 小星星 (树形dp+容斥原理+状态压缩) 题面 给出一棵树和一个图,点数均为n,问有多少种方法把树的节点标号,使得对于树上的任意两个节点u,v,若树上u ...
 - bzoj 4573 大森林
		
bzoj 4573 大森林 由于树上路径是唯一的,查询合法的两个点间路径长度显然与其他加点操作无关,所以可以离线处理,将所有的查询放在加点后. 这样我们可以对每棵树都在上颗树的基础上处理好形态后,处理 ...
 - 【刷题】BZOJ 4573 [Zjoi2016]大森林
		
Description 小Y家里有一个大森林,里面有n棵树,编号从1到n.一开始这些树都只是树苗,只有一个节点,标号为1.这些树都有一个特殊的节点,我们称之为生长节点,这些节点有生长出子节点的能力.小 ...
 - bzoj 4573: [Zjoi2016]大森林
		
Description 小Y家里有一个大森林,里面有n棵树,编号从1到n.一开始这些树都只是树苗,只有一个节点,标号为1.这些树 都有一个特殊的节点,我们称之为生长节点,这些节点有生长出子节点的能力. ...
 - BZOJ4573:[ZJOI2016]大森林——题解
		
http://www.lydsy.com/JudgeOnline/problem.php?id=4573 https://www.luogu.org/problemnew/show/P3348#sub ...
 - @loj - 2092@ 「ZJOI2016」大森林
		
目录 @description@ @solution@ @accepted code@ @details@ @description@ 小 Y 家里有一个大森林,里面有 n 棵树,编号从 1 到 n. ...
 - [BZOJ 1412][ZJOI 2009] 狼和羊的故事
		
题目大意 有一个 (n times m) 的网格,每一个格子上是羊.狼.空地中的一种,羊和狼可以走上空地.现要在格子边上建立围栏,求把狼羊分离的最少围栏数. (1 leqslant n, ; m le ...
 - 「ZJOI2016」大森林 解题报告
		
「ZJOI2016」大森林 神仙题... 很显然线段树搞不了 考虑离线操作 我们只搞一颗树,从位置1一直往后移动,然后维护它的形态试试 显然操作0,1都可以拆成差分的形式,就是加入和删除 因为保证了操 ...
 - [ZJOI2016]大森林(LCT)
		
题目描述 小Y家里有一个大森林,里面有n棵树,编号从1到n.一开始这些树都只是树苗,只有一个节点,标号为1.这些树都有一个特殊的节点,我们称之为生长节点,这些节点有生长出子节点的能力. 小Y掌握了一种 ...
 
随机推荐
- 深入redis内部--实现字符串
			
redis字符串的定义和实现在Ssd.h和Ssd.c中. 1.定义 typedef char *sds; //本质是字符char的指针 2.字符串的操作 sds sdsnew(const char * ...
 - 费了个劲的git
			
终于学会了git,表示不容易,个人表示总结一下... 1.设置个人PC上的git master路径,ok没问题,用时1min 2.从码云代码仓库克隆已写代码,也很简单,用时2min 3.将本机器内文 ...
 - [Mysql 查询语句]——查询指定记录
			
#比较 等于; 大于; 小于; 小于或等于; 大于或等于; 不等于; 排除掉; #指定范围查询 BETWEEN IN ; ; #指定集合查询 IN ,); ,); 集合元素可以是字符串类型 selec ...
 - SQL Serever学习4
			
SQL Server系统中数据库相关概念 在SQLServer数据库系统中分为2大类,系统数据库和用户数据库. SQLServer安装后系统会自动生成4个系统数据库,他们是Master,Model,M ...
 - jQuery基础---动画效果
			
内容摘要: 1.显示.隐藏 2.滑动.卷动 3.淡入.淡出 4.自定义动画 5.列队动画方法 6.动画相关方法 7.动画全局属性 发文不易,转载请注明出处~ 一.显示.隐藏 jQuery 中显示方法 ...
 - 京东-Java开发工程师-一面
			
时间:2017-4-7 16:47 时长:32分19秒 类型:笔试前电话面试 之前打过一个电话过来说了一声,下午就直接打过来面试了,没有自我介绍貌似 1. 你做的这些东西是什么样的? 2. 选一个你觉 ...
 - html全局属性(收藏)
			
HTML 属性赋予元素意义和语境. 下面的全局属性可用于任何 HTML 元素. 参考链接:http://www.w3school.com.cn/tags/html_ref_standardattrib ...
 - 4 springboot 集成swagger2
			
Swagger:实时生成在线接口文档,方便测试和沟通 官网地址:https://swagger.io/ 引入依赖 <dependency> <groupId>io.spring ...
 - java核心技术-IO
			
一 .IO 1.1 流的简单介绍和分类 Java流操作的相关的类和接口: Java流类图结构: 四个抽象基类分别为:InputStream .OutputStream .Reader .Writer: ...
 - 解决Fiddler无法捕获本地HttpWebRequest(C#.net)请求和HttpURLConnection(Java)请求
			
方法很简单,就是设置本地代理 C# HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url); req.Proxy = new WebPr ...