洛谷P3348 [ZJOI2016]大森林 [LCT]
刷了那么久水题之后终于有一题可以来写写博客了。
但是这题太神仙了我还没完全弄懂……
upd:写完博客之后似乎懂了。
思路
首先很容易想到\(O(n^2\log n)\)乘上\(O(\frac{n}{\log n})\)的巨大常数的暴力做法(雾
然后可以发现这题支持把询问抽离出来最后做,那么我们可以先只关注修改操作。
可以发现一个点在\([l,r]\)的树上连上去和在所有树上都连上去其实没有太大区别,只是修改生长节点时要判一下是否存在,其他时候其实可以每一棵树上都连一个,因为不存在的点并不会被询问到。
发现这题主要恶心在1操作(也就是更换生长节点),如果没有1操作那么所有树的形态就都一样了。
考虑用某种方法使得我们可以只维护一棵树,然后在这一棵树上面实现从\(1\)到\(n\)的灵活转换。
其实也不用太过灵活,只需要能够从\(i\)快速推到\(i+1\)即可。
既然1操作恶心,那么我们重点分析1操作。
考虑\([l,r],x\)的这样一次操作,对\(l,l-1\)这两棵树会造成什么差异。假设\(l-1\)的生长节点为\(y\)。
那么这就相当于把接下来所有连在\(y\)上面的节点以及它们的子树全都接到\(x\)上。(注意:不管它们是否真的有在\(x\)树上,这对答案没有影响)
同理,\(r,r+1\)之间的差异相当于接回去。
那么我们只要迅速地修改一堆子树的父亲即可。
考虑在一个虚点下面连上这一堆子树,那么每次就只需要修改虚点的父亲就好了。
每当有一个新的1操作\([l,r],x\)时就新开一个虚点\(p\),表示接下来所有0操作的点都连到\(p\)上,同时\(p\)也要连到上一个虚点上,保证之前的虚点也连上了所有点。
\(p\)在\(l\)树之前一直连在原虚点上,\([l,r]\)时连在\(x\)上,之后再连回去。
然后就可以从\(1\)到\(n\)处理这棵树上的修改和询问,然后就做完了。
我不敢确定自己是否真的理解了做法,请发现思路上的错误的大佬指正,谢谢!
代码
注意这里没有保存一个点在原树上的父亲是谁,所以不能随便\(\text{makeroot}\)破坏LCT上的父子结构。
但是我还是不怎么明白\(\text{cut}\)函数中为什么\(lson(x)\)一定是\(x\)在原树中的父亲,希望有大佬赐教。
#include<bits/stdc++.h>
clock_t t=clock();
namespace my_std{
using namespace std;
#define pii pair<int,int>
#define fir first
#define sec second
#define MP make_pair
#define rep(i,x,y) for (int i=(x);i<=(y);i++)
#define drep(i,x,y) for (int i=(x);i>=(y);i--)
#define go(x) for (int i=head[x];i;i=edge[i].nxt)
#define templ template<typename T>
#define sz 404040
typedef long long ll;
typedef double db;
mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());
templ inline T rnd(T l,T r) {return uniform_int_distribution<T>(l,r)(rng);}
templ inline bool chkmax(T &x,T y){return x<y?x=y,1:0;}
templ inline bool chkmin(T &x,T y){return x>y?x=y,1:0;}
templ inline void read(T& t)
{
t=0;char f=0,ch=getchar();double d=0.1;
while(ch>'9'||ch<'0') f|=(ch=='-'),ch=getchar();
while(ch<='9'&&ch>='0') t=t*10+ch-48,ch=getchar();
if(ch=='.'){ch=getchar();while(ch<='9'&&ch>='0') t+=d*(ch^48),d*=0.1,ch=getchar();}
t=(f?-t:t);
}
template<typename T,typename... Args>inline void read(T& t,Args&... args){read(t); read(args...);}
char __sr[1<<21],__z[20];int __C=-1,__zz=0;
inline void Ot(){fwrite(__sr,1,__C+1,stdout),__C=-1;}
inline void print(register int x)
{
if(__C>1<<20)Ot();if(x<0)__sr[++__C]='-',x=-x;
while(__z[++__zz]=x%10+48,x/=10);
while(__sr[++__C]=__z[__zz],--__zz);__sr[++__C]='\n';
}
void file()
{
#ifndef ONLINE_JUDGE
freopen("a.in","r",stdin);
#endif
}
inline void chktime()
{
#ifndef ONLINE_JUDGE
cout<<(clock()-t)/1000.0<<'\n';
#endif
}
#ifdef mod
ll ksm(ll x,int y){ll ret=1;for (;y;y>>=1,x=x*x%mod) if (y&1) ret=ret*x%mod;return ret;}
ll inv(ll x){return ksm(x,mod-2);}
#else
ll ksm(ll x,int y){ll ret=1;for (;y;y>>=1,x=x*x) if (y&1) ret=ret*x;return ret;}
#endif
// inline ll mul(ll a,ll b){ll d=(ll)(a*(double)b/mod+0.5);ll ret=a*b-d*mod;if (ret<0) ret+=mod;return ret;}
}
using namespace my_std;
int n,m;
int fa[sz],ch[sz][2],w[sz],sum[sz];
#define ls ch[x][0]
#define rs ch[x][1]
void pushup(int x){sum[x]=w[x]+sum[ls]+sum[rs];}
bool get(int x){return ch[fa[x]][1]==x;}
bool nroot(int x){return ch[fa[x]][0]==x||ch[fa[x]][1]==x;}
void rotate(int x)
{
int y=fa[x],z=fa[y],k=get(x),w=ch[x][!k];
if (nroot(y)) ch[z][get(y)]=x;ch[x][!k]=y;ch[y][k]=w;
if (w) fa[w]=y;fa[y]=x;fa[x]=z;
pushup(y);pushup(x);
}
void splay(int x){for (int y;(y=fa[x],nroot(x));rotate(x)) if (nroot(y)) rotate(get(x)==get(y)?y:x);}
int access(int x){int y=0;for (;x;x=fa[y=x]) splay(x),rs=y,pushup(x);return y;}
void link(int x,int y){splay(x);fa[x]=y;}
void cut(int x){access(x);splay(x);fa[ls]=0;ls=0;pushup(x);}
int query(int x,int y)
{
int ret=0;
access(x);splay(x);ret+=sum[x];
int lca=access(y);
splay(y);
ret+=sum[y];
access(lca);splay(lca);ret-=2*sum[lca];
return ret;
}
int L[sz],R[sz]; // x点存在的区间
int pos[sz]; // x在LCT中的编号
struct hh
{
int pos,x,y,id;
const bool operator < (const hh &y) const { return pos==y.pos?id<y.id:pos<y.pos; }
}q[sz];int c;
int ans[sz];
int main()
{
file();
read(n,m);
int ima,real,cnt; // 当前虚点、实点编号,总编号
ima=2;real=1;cnt=2;
L[1]=1;R[1]=n;pos[1]=1;w[1]=sum[1]=1;
link(2,1);
int opt,x,y,z,l,r;
rep(i,1,m)
{
read(opt);
if (opt==0)
{
read(l,r);
x=++real;pos[x]=++cnt;w[pos[x]]=sum[pos[x]]=1;
link(pos[x],ima);
L[x]=l,R[x]=r;
}
else if (opt==1)
{
read(l,r,x);
l=max(l,L[x]);r=min(r,R[x]);
if (l>r) continue;
z=++cnt;link(z,ima);
q[++c]=(hh){l,z,pos[x],i-m};
q[++c]=(hh){r+1,z,ima,i-m};
ima=z;
}
else read(z,x,y),q[++c]=(hh){z,pos[x],pos[y],i};
}
sort(q+1,q+c+1);
memset(ans,-1,sizeof(ans));
rep(i,1,c)
if (q[i].id<=0) cut(q[i].x),link(q[i].x,q[i].y);
else ans[q[i].id]=query(q[i].x,q[i].y);
rep(i,1,m) if (ans[i]!=-1) printf("%d\n",ans[i]);
return 0;
}
洛谷P3348 [ZJOI2016]大森林 [LCT]的更多相关文章
- 洛谷P3348 [ZJOI2016]大森林(LCT,虚点,树上差分)
洛谷题目传送门 思路分析 最简单粗暴的想法,肯定是大力LCT,每个树都来一遍link之类的操作啦(T飞就不说了) 考虑如何优化算法.如果没有1操作,肯定每个树都长一样.有了1操作,就来仔细分析一下对不 ...
- ●洛谷P3348 [ZJOI2016]大森林
题链: https://www.luogu.org/problemnew/show/P3348 题解: LCT,神题 首先有这么一个结论: 每次的1操作(改变生长点操作),一定只会会对连续的一段区间产 ...
- [ZJOI2016]大森林(LCT)
题目描述 小Y家里有一个大森林,里面有n棵树,编号从1到n.一开始这些树都只是树苗,只有一个节点,标号为1.这些树都有一个特殊的节点,我们称之为生长节点,这些节点有生长出子节点的能力. 小Y掌握了一种 ...
- P3348 [ZJOI2016]大森林
\(\color{#0066ff}{ 题目描述 }\) 小Y家里有一个大森林,里面有n棵树,编号从1到n.一开始这些树都只是树苗,只有一个节点,标号为1.这些树都有一个特殊的节点,我们称之为生长节点, ...
- P3348 [ZJOI2016]大森林(LCT)
Luogu3348 BZOJ4573 LOJ2092 题解 对于每个\(1\)操作建一个虚点,以后的\(0\)操作都连在最近建好的虚点上.这样每次整体嫁接的时候,直接把这个虚点断掉它原来的父亲,再\( ...
- 洛谷P2387 [NOI2014]魔法森林(LCT)
魔法森林 题目传送门 解题思路 把每条路按照\(a\)的值从小到大排序.然后用LCT按照b的值维护最小生成树,将边按照顺序放入.如果\(1\)到\(n\)有了一条路径,就更新最小答案.这个过程就相当于 ...
- bzoj 4573: [Zjoi2016]大森林 lct splay
http://www.lydsy.com/JudgeOnline/problem.php?id=4573 http://blog.csdn.net/lych_cys/article/details/5 ...
- P3348 [ZJOI2016]大森林(Link-cut-tree)
传送门 题解 题面大意: \(0.\)区间加节点 \(1.\)区间换根 \(2.\)单点询问距离 如果没有\(1\)操作,因为区间加节点都是加在下面,所以我们可以直接把\(n\)棵树压成一棵树,直接询 ...
- 洛谷 2387 NOI2014魔法森林 LCT
[题解] 我们先把边按照$a$值从小到大排序,并按照这个顺序加边. 如果当前要加入的边连接的两点$u$与$v$已经是连通的,那么直接加入这条边就会出现环.这时我们需要删除这个环中$b$值最大的边.因此 ...
随机推荐
- vhdl 数组
TYPE matrix_index is array (511 downto 0) of std_logic_vector(7 downto 0);signal cnt_freq : matrix_i ...
- 基于JavaCv并发读取本地视频流并提取每帧32位dhash特征
1.读取本地视频流,pom依赖 依赖于 org.bytedeco下的javacv/opencv/ffmpeg 包 <dependency> <groupId>org.byted ...
- mpvue——修改第三方组件样式
前言 我们都知道在vue中可以定义多个<style>,一般为了防止全局污染,我们会使用<style scoped>代表这里面的css样式只在本页面生效. 全局 这个当时测试是直 ...
- Flask 框架介绍
FLASK 框架 框架介绍 简单来说: 小 扩展性极强 官方文档 点这里 Flask 和 Django 的区别 Django 无socke,依赖第三方模块wsgi 路由系统(CBV,FBV) 中间件, ...
- 利用 Python_tkinter 完成 2048 游戏
成品展示 具备基本的数据合并以及分数统计,不同数字的色块不同 产生随机数, 数据无法合并判定以及重新开始选项 同时可以判定游戏失败条件 需求分析 完成基本数据合并算法 游戏结束条件 界面展示 重置按钮 ...
- GateOne Web SSH 环境搭建
环境配置安装python及tornadoyum -y install python-pippip install tornado GateOne安装下载源码:git clone https://git ...
- LOJ #2719. 「NOI2018」冒泡排序(组合数 + 树状数组)
题意 给你一个长为 \(n\) 的排列 \(p\) ,问你有多少个等长的排列满足 字典序比 \(p\) 大 : 它进行冒泡排序所需要交换的次数可以取到下界,也就是令第 \(i\) 个数为 \(a_i\ ...
- Codeforces 1077F2 Pictures with Kittens (hard version)(DP+单调队列优化)
题目链接:Pictures with Kittens (hard version) 题意:给定n长度的数字序列ai,求从中选出x个满足任意k长度区间都至少有一个被选到的最大和. 题解:数据量5000, ...
- virtual-dom
virtual-dom的历史 react最早研究virtual-dom,后来react火了之后,大家纷纷研究react的高性能实现,出现了2个流派,一是研究react算法的算法派,(virtual-d ...
- Day048--jQuery自定义动画和DOM操作
内容回顾 BOM location.reload() 全局刷新页面 location.href location.hash location.pathname location.hostname lo ...