• 题解 CF1599I Desert


    CF1599I Desert / 原题链接

    题意

    仙人掌是一张无向连通图,在一个仙人掌上,任意一条边至多只会出现在一个环上。

    荒漠是一张无向图,一个荒漠的每个极大连通分量都是一个仙人掌。

    给定一个 \(n\) 个点 \(m\) 条边的无向图,求有多少对 \(l,r\in [1,m]\),使得只保留编号在 \([l,r]\) 中的边后,图变成荒漠。

    数据范围:\(n\le 2.5\times 10^5,m\le 5\times 10^5\)

    解法

    转化一下题目,运用尺取法(Two-pointer),维护两个指针 \(l,r\),代表现在 \([l,r]\) 是合法的区间。那么 \(r\) 右移时,我们加边; \(l\) 右移时,我们删边。

    然后就可以用 Link Cut Cactus 去维护加边、删边了。但是我不会 LCC,于是用 LCT 去维护。

    LCT 不支持维护边的信息,将边拆点即可,设为 \(E\) 点。

    LCT 上维护两个值 \(val=0/1,tot\),依次代表 \(E\) 点是否在某个环中,这个点的子树是否有在环中的 \(E\) 点。

    加边

    设加第 \(r\) 条边 \((x,y)\)。如果 \(x,y\) 不连通,直接加入即可。如果联通,查询 \(x,y\) 间是否有在环中的 \(E\) 点(即查询 \(x\sim y\) 的 Splay 根节点的 \(tot\))。

    • *如果没有,那么加边后这些 \(E\) 点都变成环中边了。将它们的 \(val\) 设成 1,用 lazytag 维护。同时维护一个值 \(id\),代表 \(val=1\) 是来自于哪条边。即将 \(x\sim y\)\(id:=r\),同样用 lazytag 维护,具体原因下文会讲。
    • 如果有,那么加边会导致会有 \(E\) 点对应的边同时处于两个环中(一个是现在加的边,一个是以前让它的 \(val=1\) 的边),就不是荒漠了。不断地 \(l:=l+1\),删边,直到查询到这种情况不出现为止。

    删边

    设删第 \(l\) 条边 \((x,y)\)。如果 \(x,y\) 联通,那么就直接剪断。

    但是这样做会有错误。如果以前 \(x,y\) 在一个环中,但是环边没有加入 LCT(上述带 * 的情况)那么会造成信息的丢失。我们把根的 \(id\) 对应的边再加入进 LCT 即可,同时要清零 \(id\)

    如果不连通,那么这个边加边时就是带 * 的情况。我们把 \(x\sim y\) 的 Splay, \(val:=0,id:=0\) 即可。用 lazytag 维护。

    时间复杂度 \(O(nlogn)\)。(\(n\) 为拆点后的总点数)

    代码

    #include<bits/stdc++.h>
    #define rep(i,x,y) for(int i=x;i<=y;++i)
    #define lsn(o) tre[o].son[0]
    #define rsn(o) tre[o].son[1]
    using namespace std;
    const int n7=1012345,m7=n7;
    struct dino{int x,y;}e[m7];
    struct mist{int fa,son[2];int laz,id,lazid;bool val,tot,fp;}tre[n7];
    int n,m;long long ans;
    
    int rd(){
    	int shu=0;bool fu=0;char ch=getchar();
    	while( !isdigit(ch) ){if(ch=='-')fu=1;ch=getchar();}
    	while( isdigit(ch) )shu=(shu<<1)+(shu<<3)+ch-'0',ch=getchar();
    	return fu?-shu:shu;
    }
    
    void updat(int o){
    	tre[o].tot=(tre[lsn(o)].tot|tre[rsn(o)].tot);
    	if(o>n)tre[o].tot|=tre[o].val;
    }
    
    void pudown(int o){
    	if(tre[o].laz==1){
    		tre[lsn(o)].val=tre[rsn(o)].val=1;
    		tre[lsn(o)].tot=tre[rsn(o)].tot=1;
    		tre[lsn(o)].laz=tre[rsn(o)].laz=1;
    	}
    	if(tre[o].laz==-1){
    		tre[lsn(o)].val=tre[rsn(o)].val=0;
    		tre[lsn(o)].tot=tre[rsn(o)].tot=0;
    		tre[lsn(o)].laz=tre[rsn(o)].laz=-1;
    	}
    	tre[o].laz=0;
    	
    	if(tre[o].lazid>0){
    		tre[lsn(o)].id=tre[rsn(o)].id=tre[o].lazid;
    		tre[lsn(o)].lazid=tre[rsn(o)].lazid=tre[o].lazid;
    	}
    	if(tre[o].lazid==-1){
    		tre[lsn(o)].id=tre[rsn(o)].id=0;
    		tre[lsn(o)].lazid=tre[rsn(o)].lazid=-1;
    	}
    	tre[o].lazid=0;
    
    	if(tre[o].fp){
    		tre[lsn(o)].fp^=1,tre[rsn(o)].fp^=1;
    		swap( lsn(o),rsn(o) );
    		tre[o].fp=0;
    	}
    	
    	tre[0]=(mist){0,{0,0},0,0,0,0,0,0};
    }
    
    bool Dwhi(int o){return rsn(tre[o].fa)==o;}
    
    bool izrot(int o){return lsn(tre[o].fa)==o||rsn(tre[o].fa)==o;}
    
    void rota(int o){
    	int y=tre[o].fa,z=tre[y].fa,whi=Dwhi(o);
    	int fawhi=(izrot(y)?Dwhi(y):-1),v=tre[o].son[!whi];
    	tre[v].fa=y,tre[y].son[whi]=v;
    	tre[y].fa=o,tre[o].son[!whi]=y;
    	tre[o].fa=z;if(~fawhi)tre[z].son[fawhi]=o;
    	updat(y),updat(o);
    }
    
    void puall(int o){
    	if( izrot(o) )puall(tre[o].fa);
    	pudown(o);
    }
    
    void splay(int o){
    	puall(o);
    	while( izrot(o) ){
    		int y=tre[o].fa;
    		if( izrot(y) ){
    			Dwhi(o)==Dwhi(y)?rota(y):rota(o);
    		}
    		rota(o);
    	}
    }
    
    void aces(int o){
    	int las=0;
    	while(o){
    		splay(o),rsn(o)=las,updat(o);
    		las=o,o=tre[o].fa;
    	}
    }
    
    void Mroot(int o){
    	aces(o),splay(o),tre[o].fp^=1;
    }
    
    int Froot(int o){
    	aces(o),splay(o);
    	while( lsn(o) )pudown(o),o=lsn(o);
    	splay(o);
    	return o;
    }
    
    void split(int o1,int o2){
    	Mroot(o1),aces(o2),splay(o2);
    }
    
    void link(int o1,int o2){
    	if( Froot(o1)==Froot(o2) )return;
    	Mroot(o1),tre[o1].fa=o2;
    }
    
    bool cancut(int o1,int o2){
    	if( Froot(o1)^Froot(o2) )return 0;
    	split(o1,o2);
    	if( tre[o1].fa^o2 || lsn(o1) || rsn(o1) )return 0;
    	return 1;	
    }
    
    void cut(int o1,int o2){
    	if( cancut(o1,o2) )tre[o1].fa=lsn(o2)=0,updat(o2);
    }
    
    int Gdot(int now){
    	pudown(now);
    	int o=lsn(now);
    	while(o){
    		if(o>n)return o;
    		else pudown(o),o=rsn(o);
    	}	
    	o=rsn(now);
    	while(o){
    		if(o>n)return o;
    		else pudown(o),o=lsn(o);
    	}
    	return 0;
    }
    
    bool check(int o1,int o2){
    	if( Froot(o1)^Froot(o2) )return 1;
    	split(o1,o2);int o=Gdot(o2);splay(o);
    	if(!tre[o].tot)return 1;
    	return 0;
    }
    
    int main(){
    	n=rd(),m=rd();
    	rep(i,1,m)e[i]=(dino){rd(),rd()};
    	for(int l=1,r=0;r<=m;++r){
    		ans=ans+r-l+1;
    	//	printf("!%d %d\n",l,r);
    		if(r==m)break; 
    		while( !check(e[r+1].x,e[r+1].y) ){
    			if( !cancut(e[l].x,l+n)  ){
    				split(e[l].x,e[l].y);
    				int o=Gdot(e[l].y);
    				if(o){
    					splay(o);
    					tre[o].val=tre[o].tot=0,tre[o].laz=-1;
    					tre[o].id=0,tre[o].lazid=-1;
    				}
    			}
    			else{
    				int z=tre[l+n].id;
    				cut(e[l].x,l+n),cut(l+n,e[l].y);
    				if(z){					
    					link(e[z].x,z+n),link(z+n,e[z].y);
    					split(e[l].x,e[l].y);
    					int o=Gdot(e[l].y);
    					if(o){
    						splay(o);
    						tre[o].val=tre[o].tot=0,tre[o].laz=-1;
    					}
    				}
    			}
    			l++;
    		}
    		if( Froot(e[r+1].x)==Froot(e[r+1].y) ){
    			split(e[r+1].x,e[r+1].y);
    			int o=Gdot(e[r+1].y);
    			if(o){
    				splay(o);			
    				tre[o].val=tre[o].tot=1,tre[o].laz=1;
    				tre[o].id=tre[o].lazid=r+1;
    			}
    		}
    		else{
    			link(e[r+1].x,r+1+n),link(r+1+n,e[r+1].y);		
    		}
    	}
    	printf("%lld",ans);
    	return 0;
    }
    
  • 相关阅读:
    C#三元运算符
    WIN系统查询版本
    C# switch 语句
    C#反编译
    AssemblyInfo.cs 文件信息
    win系统如何在桌面显示我的电脑
    MVC传值前台
    js去除html标记
    打开页面跳转到区域下的控制器
    Hive常用操作之数据导入导出
  • 原文地址:https://www.cnblogs.com/BlankAo/p/15826654.html
Copyright © 2020-2023  润新知