• 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
    ",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 ightarrow 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('
    ');
    }
    

    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
    ",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]最小生成树

    题意:给一个无向图,每次询问仅保留权值在 (lsim 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
    ",lst=query(rt[p],1,nn,l,r));
    	}
    }
    

    5.4 利用 access 复杂度的数据结构题

    经典例题:「SDOI2017」树点涂色

    (题解鸽了鸽了)

    5.5 LCT的其他题目

    LCT还有其他一些有趣的题目。以及一些综合题杂题经常涉及LCT!

    待补充

  • 相关阅读:
    打包应用和构建Docker镜像(docker在windows上)
    Docker 在Windows上的安装
    Docker Zero Deployment and Secrets (二)
    Docker Zero Deployment and Secrets (一)
    系统基础信息模块psutil之获取系统性能篇
    IPython的安装方法
    用三种方法设置CentOS7使用代理服务器上网
    CentOS7修改pypi源
    CentOS7升级Python3
    CPU频率的简单测试工具
  • 原文地址:https://www.cnblogs.com/farway17/p/10425532.html
Copyright © 2020-2023  润新知