动态图连通性小记

由于我不会 LCT,所以所有动态图连通性问题只能分治解决。

大众版本的线段树分治已经具有比较强的扩展性。

但是今天做 Communication Towers 时发现了一篇老博客,其中介绍了一种在一些方面上更具扩展性的另类线段树分治算法(因为其更加接近于暴力)。

首先是“加边-删边-连通块个数”的普通图连通性。同样是先转化为每条边的起止日期为 \([l_i,r_i]\),然后在线段树上找到组成它的 \(\log\) 个区间,把 \(i\) 存上去。
现在开始递归分治。对于一开始的区间 \([1,Q]\),我们把所有存在这里的边和其端点都取出来,这样会形成若干个新的连通块。设当前\(i\) 所属的连通块编号为 \(bel_i\)。对于所有新产生的连通块,我们开一个新点,然后修改 \(bel\) 的一些位置,用一个在这一层局部定义的 vector 存下这些位置以及先前的值。同时更新目前新图中的点数 \(cnt\)。递归左右部分(只是取出边 \((u,v)\) 时应该视作 \((bel_u,bel_v)\)),到叶子时将答案设为彼时的 \(cnt\)。回溯时将 \(bel\) 用 vector 里的信息还原。

由于拆分成的子线段的条数为 \(O(m\log q)\),而每条线段都只用到了存在它那儿的线段的端点,因此时空复杂度均为 \(O(m\log q)\)

再来讲讲维护动态图双连通性的相似算法。假设题目是“加边-删边-桥的个数”。
递归分治部分,我们每次对关键点进行一轮缩点,然后修改 \(bel_i\)。回溯是类似的。到了叶子的时候,图的形态是一个森林,森林的边数 = 点数 - 连通块个数。因此还需要使用上一部分的方法顺带维护连通块的个数。

最后来讲讲 Communication Towers 这题。很不幸的是,我一开始写了一个普通线段树分治,用了类似带撤销的启发式合并的方式来支持对历史上所有曾经在并查集中与 \(1\) 属于同一个连通块的点打上标记。然而它 TLE on test 67,我从早上调到下午(今天是失败的一天),一直下意识地认为 4 秒时限应该对于这个算法绰绰有余,但当我在 17 点多最后提交了一发将 vector 换成手写双向链表的超长代码后,我终于承认了它的常数确实无可救药地大。

看官方题解未果,最终结合洛谷上唯一一篇题解的代码理解了在可撤销并查集上打神秘标记这个神秘的做法,感到很巧妙。
当我们将 \(v\) 合并到 \(u\) 上来时,先将 t[v]-=t[u],如果发现 find(1)==u,则需要将 t[u]++;将 \(v\)\(u\) 上分开时,将 t[v]+=t[u]。不难发现,这样子 t[v] 的增加量就是在其与 t[u] 连通的这段时间内与 \(1\) 在一个连通块的时刻数。而由于末了并查集都被撤销干净了,所以全是孤点,每个点的值是否为 0 就是答案。

//start coding at 7:33
#include <bits/stdc++.h>
using namespace std;
namespace IO {
const int buflen=1<<21;
char ch,buf[buflen],*p1=buf,*p2=buf;
int x,f;
inline char gc(){
	return p1==p2&&(p2=buf+fread(p1=buf,1,buflen,stdin),p1==p2)?EOF:*p1++;
}
inline int read(){
	x=0,f=1;ch=gc();
	while(ch<'0'||ch>'9'){if(ch=='-')f=0;ch=gc();}
	while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=gc();
	return f?x:-x;
}
}
using IO::read;
void print(int x){if(x/10)print(x/10);putchar(x%10+48);}
const int N=2e5+5,M=8e5+5;
int n,m;
int cntG,toG[M],nxtG[M],headG[N],fa[N],gao[N],vis[N],bk[N],t[N];
vector<int>vec[N<<2];
int tp;
struct stacknode {
	int u,v,t;
}stk[M];
inline void adde(int u,int v){
	cntG++;
	nxtG[cntG]=headG[u];
	toG[cntG]=v;
	headG[u]=cntG;
	cntG++;
	nxtG[cntG]=headG[v];
	toG[cntG]=u;
	headG[v]=cntG;
}
void chg(int L,int R,int id,int l,int r,int k){//cerr<<l<<' '<<r<<'\n';
	if(L<=l&&r<=R){
		vec[k].emplace_back(id);
		return;
	}
	int mid=l+r>>1;
	if(L<=mid)chg(L,R,id,l,mid,k<<1);
	if(R>mid)chg(L,R,id,mid+1,r,k<<1|1);
}
int find(int x){
	while(fa[x]!=x)x=fa[x];
	return x;
}
void unite(int u,int v){
	u=find(u),v=find(v);
	if(u==v)return;
	if(gao[u]<gao[v])swap(u,v);
	stk[++tp]=stacknode{u,v,gao[u]==gao[v]};
	if(gao[u]==gao[v])gao[u]++;
	fa[v]=u;t[v]-=t[u];
	if(find(1)==u)t[u]++;
}
void chexiao(){
	int u=stk[tp].u,v=stk[tp].v;
	t[v]+=t[u];
	fa[v]=v;
	if(stk[tp].t)gao[u]--;
	tp--;
}
void dfs(int l,int r,int k){
	int init=tp;
	for(int u:vec[k]){
		vis[u]=1;
		for(int i=headG[u];i;i=nxtG[i])if(vis[toG[i]]){
			unite(u,toG[i]);
			//if(hao[4]==1)cerr<<l<<' '<<r<<'L';
			//cerr<<hao[4]<<';';
		}
	}
	if(l<r){
		int mid=l+r>>1;
		dfs(l,mid,k<<1);
		dfs(mid+1,r,k<<1|1);
	}
	while(tp>init)chexiao();
	for(int u:vec[k])vis[u]=0;
}
int main(){
	//freopen("CT.in","r",stdin);freopen("CT.out","w",stdout);
	n=read(),m=read();
	for(int i=1,l,r;i<=n;i++){
		l=read(),r=read();
		chg(l,r,i,1,2e5,1);
	}
	for(int i=1,u,v;i<=m;i++){
		u=read(),v=read(); 
		adde(u,v);
	}
	for(int i=1;i<=n;i++)fa[i]=i;
	dfs(1,2e5,1);
	t[1]=1;
	for(int i=1;i<=n;i++)if(t[i])print(i),putchar(' ');
	return 0;
}
posted @ 2023-05-12 08:03  pengyule  阅读(222)  评论(0编辑  收藏  举报