LCT(2)
LCT(2)
关于 LCT 的基本操作和代码实现见 (1) 。
5. LCT的应用
5.0 LCT 裸题
就是LCT的基本操作模板题,常出现于早年省选。不讨论。
5.1 LCT维护子树信息
很多时候,我们需要用 LCT 来维护子树的信息。
5.1.1 例1:[BJOI2014] 大融合
这题要求我们支持动态维护子树大小。我们对于每个点分别维护实子树和和虚子树和即可。
具体见代码。没注释的都是板子。
#include<cstdio>
inline int gi()
{
char c; int x=0;
for(;c<'0'||c>'9';c=getchar());
for(;c>='0'&&c<='9';c=getchar())x=(x<<1)+(x<<3)+c-'0';
return x;
}
const int N=100005;
int ch[N][2],fa[N],n,m,w[N],st[N],size[N],isze[N];
bool rev[N];
#define isnrt(x) (ch[fa[x]][0]==x||ch[fa[x]][1]==x)
#define swap(x,y) (x^=y^=x^=y)
void pushup(int x) {
size[x]=size[ch[x][0]]+size[ch[x][1]]+isze[x]+1; //注意加上虚子树大小
}
void pushdown(int x)
{
if(rev[x])
{
rev[ch[x][0]]^=1,rev[ch[x][1]]^=1;
swap(ch[x][0],ch[x][1]);
rev[x]=0;
}
}
void rotate(int x){
int y=fa[x],z=fa[y];
bool k=ch[y][0]==x; int w=ch[x][k];
if(isnrt(y))ch[z][ch[z][1]==y]=x;ch[x][k]=y;ch[y][!k]=w;
fa[w]=y;fa[y]=x;fa[x]=z;
pushup(y); pushup(x);
}
void splay(int x)
{
int y=x,tp=1; st[1]=y;
while(isnrt(y)) st[++tp]=y=fa[y];
while(tp) pushdown(st[tp--]);
while(isnrt(x))
{
int y=fa[x],z=fa[y];
if(isnrt(y)) (ch[z][1]==y)^(ch[y][1]==x)?rotate(x):rotate(y);
rotate(x);
}
}
void access(int x)
{
int y=0;
for(;x;y=x,x=fa[x])
{
splay(x);isze[x]+=size[ch[x][1]]; //更新x虚儿子信息
ch[x][1]=y,isze[x]-=size[y];
pushup(x);
}
}
void makert(int x) {
access(x),splay(x),rev[x]^=1;
}
void split(int x, int y) {
makert(x),access(y),splay(y);
}
void link(int x, int y) {
split(x,y),fa[x]=y; //注意这里是split,要将y置为辅助树的根
isze[y]+=size[x],pushup(y); //x为y的虚儿子
}
int main()
{
n=gi(),m=gi();
while(m--)
{
char op[2]; scanf("%s",op);
int x=gi(),y=gi();
if(op[0]=='A') link(x,y);
else {
split(x,y);
printf("%lld\n",1ll*size[x]*(size[y]-size[x]));
}
}
}
本部分例题待补充。
5.2 LCT维护边权信息
LCT 的板子维护的都是点权。怎么维护边权呢?
其实很简单。连边的时候只要新建一个节点,点权为两点边权,然后与两点连边即可。
暂无例题。
5.3 LCT维护最小生成树
这个是 LCT 比较常见的应用:支持动态维护最小生成树(森林)。
按照上面的方法维护边权最大值。
加入一条边后判断有没有成环(用并查集维护),如果成环就把边权最大的边给砍掉。
5.3.1 例1 道路重建 (OJ1901)
给定一个图,每次询问只有第 \(L\) 条边到第 \(R\) 条边可用, \(A\) 点到 \(B\) 点是否连通。
Sol: 离线处理。按顺序加边维护最小生成森林,每条边的边权为该边的编号。处理到每个第 \(R\) 条边时,只需要查询 \(A\rightarrow B\) 的最小边权是否 \(>=L\) 即可。
下为代码,省略了板子部分
int fa[N],ch[N][2],st[N],val[N],mn[N],ind,f[N],pa[N],pb[N]; bool rev[N];
int findset(int x)
{
if(f[x]==x) return x;
return f[x]=findset(f[x]);
}
#define isnrt(x) (ch[fa[x]][0]==x||ch[fa[x]][1]==x)
void pushup(int x)
{
mn[x]=x;
if(val[mn[ch[x][0]]]<val[mn[x]]) mn[x]=mn[ch[x][0]];
if(val[mn[ch[x][1]]]<val[mn[x]]) mn[x]=mn[ch[x][1]];
}
//此处省略部分板子内容
int query(int x, int y) { //查询x->y的最小边权
split(x,y); return mn[y];
}
void link3(int x, int y, int w)
{
if(x==y) return ;
int fx=findset(x),fy=findset(y);
if(fx==fy)
{
int t=query(x,y);
cut(t,pa[t]),cut(t,pb[t]); //若成环,删除最小的边
}
else f[fx]=fy;
val[++ind]=w;link(x,ind),link(y,ind); //新建虚拟节点,连边
pa[ind]=x,pb[ind]=y;
}
int n,m,qn,u[N],v[N],l[N],r[N],a[N],b[N];
bool ans[N];
vector<int> ve[N];
int main()
{
memset(val,0x3f,sizeof(val));
ind=n=gi(),m=gi();
for(int i=0;i<=n;i++) mn[i]=f[i]=i;
for(int i=1;i<=m;i++) u[i]=gi(),v[i]=gi();
qn=gi();
for(int i=1;i<=qn;i++)
l[i]=gi(),r[i]=gi(),a[i]=gi(),b[i]=gi(),ve[r[i]].push_back(i);
for(int i=1;i<=m;i++)
{
link3(u[i],v[i],i);
for(int j=0;j<ve[i].size();j++)
{
int k=ve[i][j];
if(findset(a[k])!=findset(b[k])) continue;
int x=query(a[k],b[k]);
ans[k]=(l[k]<=val[x]);
}
}
for(int i=1;i<=qn;i++) putchar(ans[i]+'0'),putchar('\n');
}
5.3.2 例2 [2016清华集训]温暖会指引我们前行
题意晦涩难懂,应该是动态修改边权,两点间最大生成树上的路径。
总之就是LCT动态维护最大生成树。
//删去开头和LCT板子内容
int ch[N][2],fa[N],f[N],st[N],tem[N],len[N],mn[N],sum[N],pa[N],pb[N],ind,n,m;
bool rev[N];
#define lc ch[x][0]
#define rc ch[x][1]
#define isnrt(x) (ch[fa[x]][0]==x||ch[fa[x]][1]==x)
#define swap(x,y) (x^=y^=x^=y)
int findset(int x) {
if(f[x]==x) return x;
return f[x]=findset(f[x]);
}
void pushup(int x)
{
mn[x]=x;
if(tem[mn[lc]]<tem[mn[x]]) mn[x]=mn[lc];
if(tem[mn[rc]]<tem[mn[x]]) mn[x]=mn[rc];
sum[x]=sum[lc]+sum[rc]+len[x];
}
void work(int id, int x, int y, int t, int l)
{
int fx=findset(x),fy=findset(y);
if(fx==fy)
{
split(x,y);
int mt=mn[y];
if(tem[mt]>t) return ;
cut(mt,pa[mt]),cut(mt,pb[mt]);
}
else f[fx]=fy;
len[n+id]=l,tem[n+id]=t,link(x,n+id),link(y,n+id);
pa[n+id]=x,pb[n+id]=y;
}
int main()
{
n=gi(),m=gi();
memset(tem,0x3f,sizeof(tem));
for(int i=1;i<=n;i++) mn[i]=f[i]=i;
while(m--)
{
char op[10]; scanf("%s",op);
if(op[0]=='f')
{
int id=gi()+1,u=gi()+1,v=gi()+1,t=gi(),l=gi();
work(id,u,v,t,l);
}
if(op[0]=='m')
{
int u=gi()+1,v=gi()+1;
if(findrt(u)!=findrt(v)) {
puts("-1");
continue;
}
split(u,v);
printf("%d\n",sum[v]);
}
if(op[0]=='c')
{
int id=gi()+1,l=gi();
splay(n+id);
len[n+id]=l;
pushup(n+id);
}
}
}
5.3.3 [JSTSC2015]最小生成树
题意:给一个无向图,每次询问仅保留权值在 \(l\sim r\) 的边,形成的最小生成森林的权值和为多少。
Sol (By wyj): LCT+主席树。用LCT维护最小生成树,用主席树维护加入前 \(i\) 条边时,图中最小生成树的情况(边权降序排序)。LCT上加删第 \(k\) 条边,就在主席树上位置 \(k\) 加减权值。对于每个询问二分得到位置区间,直接查询主席树上该区间的权值和即可。
//删去开头和LCT板子内容
int ch[N][2],fa[N],st[N],f[N],pa[N],pb[N];
int rt[N*80],sum[N*80],ls[N*80],rs[N*80],val[N],mx[N],w[N];
int n,m,q,b,ind,lst,nn,cnt;
bool rev[N];
unordered_map<int,int> mp;
int update(int& x, int l, int r, int s, int w)
{
int x2=++cnt;
ls[x2]=ls[x],rs[x2]=rs[x],sum[x2]=sum[x]+w;
if(l==r) return x2;
int mid=l+r>>1;
if(s<=mid) ls[x2]=update(ls[x],l,mid,s,w);
else rs[x2]=update(rs[x],mid+1,r,s,w);
return x2;
}
int query(int x, int l, int r, int sl, int sr)
{
if(sl<=l&&r<=sr) return sum[x];
if(sl>r||sr<l) return 0;
int mid=l+r>>1;
return query(ls[x],l,mid,sl,sr)+query(rs[x],mid+1,r,sl,sr);
}
struct edge {
int u,v,w;
bool operator < (edge x) const {
return w<x.w;
}
} e[N];
int findset(int x) {
if(f[x]==x) return x;
return f[x]=findset(f[x]);
}
#define isnrt(x) (ch[fa[x]][0]==x||ch[fa[x]][1]==x)
void pushup(int x)
{
mx[x]=x;
if(val[mx[ch[x][0]]]>val[mx[x]]) mx[x]=mx[ch[x][0]];
if(val[mx[ch[x][1]]]>val[mx[x]]) mx[x]=mx[ch[x][1]];
}
int findmx(int x, int y) {
split(x,y); return mx[y];
}
void link(int id, int x, int y, int w)
{
if(x==y) return ;
int fx=findset(x),fy=findset(y);
if(fx==fy)
{
int t=findmx(x,y);
cut(t,pa[t]),cut(t,pb[t]);
rt[id]=update(rt[id],1,nn,mp[val[t]],-val[t]);
}
else f[fx]=fy;
rt[id]=update(rt[id],1,nn,mp[w],w);
val[++ind]=w;link(x,ind),link(y,ind);
pa[ind]=x,pb[ind]=y;
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("1517.in","r",stdin);
#endif
memset(val,-0x3f,sizeof(val));
ind=n=gi(),m=gi();
for(int i=0;i<=n;i++) f[i]=mx[i]=i;
for(int i=1;i<=m;i++) e[i].u=gi(),e[i].v=gi(),w[i]=e[i].w=gi();
sort(e+1,e+1+m);
for(int i=1;i<=m;i++)
{
if(e[i].w!=e[i-1].w) ++nn;
mp[e[i].w]=nn;
}
for(int i=m;i;i--)
{
rt[i]=rt[i+1];
link(i,e[i].u,e[i].v,e[i].w);
}
q=gi(),b=gi();
sort(w+1,w+1+m); int mm=unique(w+1,w+1+m)-w-1;
while(q--)
{
int l=gi()+lst*b,r=gi()+l;
int p=lower_bound(e+1,e+1+m,(edge){0,0,l})-e;
l=lower_bound(w+1,w+1+mm,l)-w;
r=upper_bound(w+1,w+1+mm,r)-w-1;
printf("%d\n",lst=query(rt[p],1,nn,l,r));
}
}
5.4 LCT的其他题目
LCT还有其他一些有趣的题目。以及一些综合题杂题经常涉及LCT!
待补充
LCT(2)的更多相关文章
- 一堆LCT板子
搞了一上午LCT,真是累死了-- 以前总觉得LCT高大上不好学不好打,今天打了几遍感觉还可以嘛= =反正现在的水平应付不太难的LCT题也够用了,就这样好了,接下来专心搞网络流. 话说以前一直YY不出来 ...
- 动态树之LCT(link-cut tree)讲解
动态树是一类要求维护森林的连通性的题的总称,这类问题要求维护某个点到根的某些数据,支持树的切分,合并,以及对子树的某些操作.其中解决这一问题的某些简化版(不包括对子树的操作)的基础数据结构就是LCT( ...
- 在此为LCT开一个永久的坑
其实我连splay都还不怎么会. 今天先抄了黄学长的bzoj2049,以后一定要把它理解了. 写LCT怎么能不%数据结构大神yeweining呢?%%%chrysanthemums %%%切掉大森林 ...
- 【BZOJ2157】旅游 LCT
模板T,SB的DMoon..其实样例也是中国好样例...一开始不会复制,yangyang:找到“sample input”按住shift,按page down.... #include <ios ...
- 【BZOJ3669】[Noi2014]魔法森林 LCT
终于不是裸的LCT了...然而一开始一眼看上去这是kruskal..不对,题目要求1->n的路径上的每个点的两个最大权值和最小,这样便可以用LCT来维护一个最小生成路(瞎编的...),先以a为关 ...
- 【BZOJ1180】: [CROATIAN2009]OTOCI & 2843: 极地旅行社 LCT
竟然卡了我....忘记在push_down先下传父亲的信息了....还有splay里for():卡了我10min,但是双倍经验还是挺爽的,什么都不用改. 感觉做的全是模板题,太水啦,不能这么水了... ...
- 【BZOJ3282】Tree LCT
1A爽,感觉又对指针重怀信心了呢= =,模板题,注意单点修改时splay就好,其实按吾本意是没写的也A了,不过应该加上能更好维护平衡性. ..还是得加上好= = #include <iostre ...
- BZOJ2888 资源运输(LCT启发式合并)
这道题目太神啦! 我们考虑他的每一次合并操作,为了维护两棵树合并后树的重心,我们只好一个一个的把节点加进去.那么这样一来看上去似乎就是一次操作O(nlogn),但是我们拥有数据结构的合并利器--启发式 ...
- LCT裸题泛做
①洞穴勘测 bzoj2049 题意:由若干个操作,每次加入/删除两点间的一条边,询问某两点是否连通.保证任意时刻图都是一个森林.(两点之间至多只有一条路径) 这就是个link+cut+find roo ...
- 链剖&LCT总结
在搞LCT之前,我们不妨再看看喜闻乐见的树链剖分. 树链剖分有一道喜闻乐见的例题:NOI2015 软件包管理器 如果你看懂题目了,你就会明白它是叫你维护一个树,这棵树是不会动的,要兹磁子树求和,子树修 ...
随机推荐
- 启动易EZB Systems EasyBoot V6.5.1.669 + 注册码
启动易EasyBoot可以简单的让您制作启动光盘,它可以制作光盘启动菜单.自动生成启动文件.并生成可启动ISO文件.只要通过CD-R/W刻录软件即可制作完全属于自己的启动光盘. EasyBoot 6. ...
- 吴裕雄 Bootstrap 前端框架开发——Bootstrap 表格:表示信息变化的操作
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...
- Vue 项目开发
目录 Vue 项目开发 项目目录结构解析 入口文件 main.js (项目入口) 根组件 app.vue index.html 文件入口 router 路由 components 子组件 项目初始化 ...
- 【剑指Offer面试编程题】题目1522:包含min函数的栈--九度OJ
题目描述: 定义栈的数据结构,请在该类型中实现一个能够得到栈最小元素的min函数. 输入: 输入可能包含多个测试样例,输入以EOF结束. 对于每个测试案例,输入的第一行为一个整数n(1<=n&l ...
- Android中的Sqlite中的onCreate方法和onUpgrade方法的执行时机--(转)
原文:http://blog.csdn.net/jiangwei0910410003/article/details/46536329 今天在做数据库升级的时候,遇到一个问题,就是onCreate方法 ...
- linux零散知识
1.Linux的“| ”管道符用法 上一条命令的输出,作为下一条命令参数 方式:command1 | command2 Linux所提供的管道符“|”将两个命令隔开,管道符左边命令的输出就会作为管道符 ...
- 转:Nginx 性能优化有这篇就够了!
目录: https://mp.weixin.qq.com/s/YoZDzY4Tmj8HpQkSgnZLvA 1.Nginx运行工作进程数量 Nginx运行工作进程个数一般设置CPU的核心或者核心数x2 ...
- Java项目xml相关配置
一.web.xml //设置会话过期时间,这里单位是分钟 <session-config> <session-timeout>30</session-timeout> ...
- PWM与时间片思想
改编自:http://www.moz8.com/thread-79049-1-1.html 什么是PWM? PWM:脉冲宽度调制,由于在数字电路(或者单片机)输出模拟信号的成本高昂,换句话说,受制于只 ...
- 域名配置DNS解析A记录,映射到主机
有很多域名的供应商,随便选,哪个便宜用哪个.godaddy一直支持支付宝,不用visa,虽然它是国外的. 我用的是godaddy,这两年有中文版的了,虽然它有了中文版,但是比以前的英文版还要慢. 进入 ...