题解 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;
}
上一篇:Collection排序工具类--CollectionSortUtil


下一篇:剑指offer Java题解之JZ86 在二叉树中找到两个节点的最近公共祖先