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)的更多相关文章

  1. 一堆LCT板子

    搞了一上午LCT,真是累死了-- 以前总觉得LCT高大上不好学不好打,今天打了几遍感觉还可以嘛= =反正现在的水平应付不太难的LCT题也够用了,就这样好了,接下来专心搞网络流. 话说以前一直YY不出来 ...

  2. 动态树之LCT(link-cut tree)讲解

    动态树是一类要求维护森林的连通性的题的总称,这类问题要求维护某个点到根的某些数据,支持树的切分,合并,以及对子树的某些操作.其中解决这一问题的某些简化版(不包括对子树的操作)的基础数据结构就是LCT( ...

  3. 在此为LCT开一个永久的坑

    其实我连splay都还不怎么会. 今天先抄了黄学长的bzoj2049,以后一定要把它理解了. 写LCT怎么能不%数据结构大神yeweining呢?%%%chrysanthemums  %%%切掉大森林 ...

  4. 【BZOJ2157】旅游 LCT

    模板T,SB的DMoon..其实样例也是中国好样例...一开始不会复制,yangyang:找到“sample input”按住shift,按page down.... #include <ios ...

  5. 【BZOJ3669】[Noi2014]魔法森林 LCT

    终于不是裸的LCT了...然而一开始一眼看上去这是kruskal..不对,题目要求1->n的路径上的每个点的两个最大权值和最小,这样便可以用LCT来维护一个最小生成路(瞎编的...),先以a为关 ...

  6. 【BZOJ1180】: [CROATIAN2009]OTOCI & 2843: 极地旅行社 LCT

    竟然卡了我....忘记在push_down先下传父亲的信息了....还有splay里for():卡了我10min,但是双倍经验还是挺爽的,什么都不用改. 感觉做的全是模板题,太水啦,不能这么水了... ...

  7. 【BZOJ3282】Tree LCT

    1A爽,感觉又对指针重怀信心了呢= =,模板题,注意单点修改时splay就好,其实按吾本意是没写的也A了,不过应该加上能更好维护平衡性. ..还是得加上好= = #include <iostre ...

  8. BZOJ2888 资源运输(LCT启发式合并)

    这道题目太神啦! 我们考虑他的每一次合并操作,为了维护两棵树合并后树的重心,我们只好一个一个的把节点加进去.那么这样一来看上去似乎就是一次操作O(nlogn),但是我们拥有数据结构的合并利器--启发式 ...

  9. LCT裸题泛做

    ①洞穴勘测 bzoj2049 题意:由若干个操作,每次加入/删除两点间的一条边,询问某两点是否连通.保证任意时刻图都是一个森林.(两点之间至多只有一条路径) 这就是个link+cut+find roo ...

  10. 链剖&LCT总结

    在搞LCT之前,我们不妨再看看喜闻乐见的树链剖分. 树链剖分有一道喜闻乐见的例题:NOI2015 软件包管理器 如果你看懂题目了,你就会明白它是叫你维护一个树,这棵树是不会动的,要兹磁子树求和,子树修 ...

随机推荐

  1. Python 中的else

    在其他程序语言中,else 似乎只是与 if 关键字有缘分.而与其他的关键字没有联系,不能搭配使用,而在python中,else 除了与 if 匹配外, 还可以与for.while/ try等关键字匹 ...

  2. Lesson 13 The search for oil

    What do oilmen want to achieve as soon as they strike oil? The deepest holes of all are made for oil ...

  3. C++ 定位错误行

    ] = {}; SYSTEMTIME st; GetLocalTime(&st); sprintf_s(buf, , "%02d-%02d-%02d %02d:%02d:%02d | ...

  4. 一、linux基础-对文件操作

    1.1文件夹创建-复制-移动-重命名-删除1.创建文件夹mkdir zjbdir 2.复制文件/文件夹复制文件到:当前目录cp -r zjbdir  zjbdir201600819复制文件到:当前目录 ...

  5. (2)LoraWAN:Lora LMIC library 编程模型及API

    二.LMIC library 编程模型及API LMiC库可以通过一组API函数(API functions),运行时函数(run-time functions),回调函数(callback func ...

  6. struts2--验证器

    1.输入验证: --struts2提供了一些基于Xwork Validation Framework的内建验证程序,使用这些验证程序不需要变编程,只要在一个XML文件里进行声明,声明的内容如下: &g ...

  7. JDBC--PreparedStatement使用

    1. PreparedStatement是Statement的子接口,可以传入传入带有占位符的SQL语句,并且提供了相应的方法来替换占位符(setXxx(int index, Object value ...

  8. saveToken介绍二

      Struts的Token(令牌)机制能够很好的解决表单重复提交的问题,基本原理是:服务器端在处理到达的请求之前,会将请求中包含的令牌值与保存在当前用户会话中的令牌值进行比较,看是否匹配.在处理完该 ...

  9. Java 变参函数的实现

      Java的变参函数实现实际上参数是一个数组,其简单用法如下 public class variableParamTest { private static void variableParam(O ...

  10. 本地Git仓库与GitHub/GitLab仓库同步

    本地仓库即为在你的电脑上的项目文件,远程仓库即为服务器仓库,如GitHub.GitLab或其他等.此处以GitHub介绍本地仓库与远程仓库的同步.可先创建本地仓库,也可先创建GitHub仓库,但都需要 ...