题解:SP10931 ONBRIDGE - Online Bridge Searching

割边和桥都只一条若被删去则原图不连通的边。题意很明了,但是如果按照题意模拟,每加一次边就暴力求桥,显然会有无法接受的复杂度。考虑进行优化。

思路

我们先以加入的时间为权值做原图的最小生成树。注意到若一条边在某段时间里对答案有正贡献,其一定是在该生成树中的,而不在此生成树中的边只可能会有负贡献。

对答案有正贡献的边可以用类似 kruskal 的做法,不需要进行排序(因为加入边是按照时间顺序加入的),直接并查集维护,若当前加入的边的两个端点不在同一连通块内即为生成树的某条边。

考虑如何计算负贡献。记 \(u,v\) 为不在生成树中的某条边的两个端点。其必然连通树上的某两个点(否则该边必然在生成树中),此时原树变为基环树,除环外其他的边仍是割边。而该环上一定有三个点,分别是 \(u\)\(v\),和 \(u,v\) 的最近公共祖先(LCA)。该环上除当前边的所有边在原图中均为割边。加入当前边后环上所有边均不是割边,故减去。

事实上我们在求出最小生成树后不会进行任何的加边操作,写成基环树只是方便理解,树还是原树。割边不计算贡献也不需要删边,打上标记就好了。

在实现过程中,我们可以用倍增法求出 \(u,v\) 的最近公共祖先,并暴力的分别从 \(u\)\(v\) 往上跳并统计边数,同时还要取消它们割边的标记,以防重复计算。因为时间限制给的很抽象,所以尽量不要偷懒用 map 维护两个端点来得到边的编号。我就是这样偷懒被卡掉了。直接标记以某个点为深度较大的那条边是否为割边即可。

码风似乎不太好,见谅。

code:

int t;
int n,m;
class node{
	public:
		int u;
		int v;
};
node ee[1000086];
int eg;
std::vector<int>e[1000086];
int ff[1000086];
int fa[1000086][30];
int dis[1000086];
bool flag[1000086];
bool flag2[1000086];
int find(int x){
	return ff[x]==x?x:ff[x]=find(ff[x]);
}
void add(int x,int y){
	x=find(x);
	y=find(y);
	if(x!=y)
		ff[x]=y;
	return;
}
void dfs(int x,int fath){
	for(int j=1;j<=25;j++)
		fa[x][j]=fa[fa[x][j-1]][j-1];//lca 的预处理
	for(int i:e[x])
		if(i!=fa[x][0] and (not dis[i])){
			fa[i][0]=x;
			dis[i]=dis[x]+1;
			flag2[i]=true;//在生成树里的所有点都会有贡献,打上标记
			dfs(i,x);
		}
	return;
}
int lca(int x,int y){//lca 板子
	if(dis[x]<dis[y])
		std::swap(x,y);
	for(int i=25;i>=0;i--)
		if(dis[x]-(1LL<<i)>=dis[y])
			x=fa[x][i];
	if(x==y)
		return x;
	for(int i=25;i>=0;i--)
		if(fa[x][i]!=fa[y][i]){
			x=fa[x][i];
			y=fa[y][i];
		}
	return fa[x][0];
}
int main(){
	std::cin>>t;//多测
	while(t--){
		std::cin>>n>>m;
		int ans=0;
		for(int i=0;i<=n;i++){
			ff[i]=i;
			e[i].clear();
		}
		memset(flag,false,sizeof flag);
		memset(dis,0,sizeof dis);
		memset(fa,0,sizeof fa);
		memset(flag2,false,sizeof flag2);//千万不要忘记初始化!
		for(int i=1;i<=m;i++){
			int x,y;
			std::cin>>x>>y;
			if(find(x)!=find(y)){//并查集维护一下
				flag[i]=true;//标记当前点为割边
				e[x].push_back(y);
				e[y].push_back(x);//建图
				add(x,y);//合并
			}
			ee[i]=(node){x,y};//存边
		}
		dfs(0,-1);//做倍增求 lca 的预处理
		for(int i=1;i<=m;i++){
			if(flag[i])
				ans++;
			else{
				int qwq=lca(ee[i].u,ee[i].v);
				for(int j=ee[i].u;j!=qwq;j=fa[j][0]){
					if(flag2[j])
						ans--;
					flag2[j]=false;
				}
				for(int j=ee[i].v;j!=qwq;j=fa[j][0]){
					if(flag2[j])
						ans--;
					flag2[j]=false;
				}//暴力往上跳
			}
			std::cout<<ans<<"\n";
		}
	}
	return 0;//撒花~
}

好像不是最优解,%%%最优解@LLCCFF

posted @ 2024-09-30 07:29  立花廿七  阅读(9)  评论(0编辑  收藏  举报