[BZOJ4668]冷战

137 篇文章 1 订阅
122 篇文章 0 订阅

题目

传送门 to BZOJ

题目概要
N N N 个点、 M M M 次操作,要么是加入一条边,要么是询问两个点在哪次操作之后联通。

数据范围与约定
N , M ≤ 5 × 1 0 5 N,M\le 5\times 10^5 N,M5×105 ,强制在线。

思路

考虑新增一条边带来的影响,本质是 将两个端点所属的连通块合并为一个连通块

所以你可以看出,加入一条边的时候,如果两个端点已经连通,那么这条边不起任何作用。

询问“何时连通”,也就是在询问,什么时候两个连通块合并成了一个连通块。

我们用一棵树来表示一个连通块。节点既可以代表“点”,也可以代表“边”。对于一棵“连通块树”,叶子是“点”,其他的节点是“边”。这样一来,将两个连通块合并,等价于新增一个非叶子节点,代表这条新增的边,其子节点为两个端点所属的“连通块树”。如图。

在这里插入图片描述

此时,两个点的 l c a lca lca 就是连通的时间

然后我们把这个过程稍稍优化。注意到 边的编号满足大根堆的性质。所以, l c a lca lca 的本质就是 两个点路径上最大的边编号

既然如此,我们就可以稍稍的旋转一下。我们在插入的时候,也就是合并两个“连通块树”的时候,可以将其中一个的根节点作为新的根节点,而儿子是新加入的边。如图。

在这里插入图片描述

有意思的是,如果总是按照这个策略进行,一定有如下性质:

  • 根节点是一个代表“点”的节点。(归纳法,最初的 n n n 个树都满足,新的根是原树的根)
  • 任意一个代表“点”的节点,其子节点为“边”。(观察 A A A 的根可知)
  • 任意一个代表“边”的节点,有且只有一个子节点,该子节点代表“点”。(观察 G G G 可知)

现在我们就可以稍微省略一些信息了。我们可以 将边权保存到其子节点上

这样一来,“连通块树”就是一个完美的树,其中只包含带权的点。查询就是找路径上的最大值。

注意到现在的连边过程,相当于是将 a a a 的根与 b b b 的根相连。并查集

启发式合并即可,因为不能路径压缩(我们要用到树上路径)。暴力爬山找路径上最大值——毕竟树高是 O ( log ⁡ n ) \mathcal O(\log n) O(logn) 的。

代码

#include <cstdio>
#include <iostream>
#include <vector>
#include <cstdlib>
using namespace std;
inline int readint(){
	int x; scanf("%d",&x); return x;
}

const int MaxN = 500005;
int h[MaxN]; // 按秩启发式合并 
int v[MaxN]; // 与父亲的连边的编号 
int fa[MaxN]; // 并查集中的父亲 

inline int findSet(int a){
	while(fa[a] != a) a = fa[a]; return a;
}
inline void unionSet(int a,int b,int id){
	a = findSet(a), b = findSet(b);
	if(a == b) return ;
	if(h[a] < h[b]) swap(a,b);
	if(h[a] == h[b]) ++ h[a];
	fa[b] = a, v[b] = id;
}
bool vis[MaxN]; // 保持长期为全0 
inline int query(int a,int b){
	if(findSet(a) != findSet(b)) return 0;
	for(int i=a; i!=fa[i]; i=fa[i])
		vis[i] = true; // 注意根节点没有赋值哦! 
	int lca, res = -1;
	for(lca=b; lca!=fa[lca]; lca=fa[lca])
		if(vis[lca]) break;
	for(int i=a; i!=lca; i=fa[i])
		res = max(res,v[i]);
	for(int i=b; i!=lca; i=fa[i])
		res = max(res,v[i]);
	for(int i=a; i!=fa[i]; i=fa[i])
		vis[i] = false; // 复原 
	return res;
}

int main(){
	int n = readint(), m = readint();
	for(int i=1; i<=n; ++i) fa[i] = i;
	int ans = 0, cntEdge = 0;
	for(int opt,a,b; m; --m){
		opt = readint();
		a = readint()^ans;
		b = readint()^ans;
		if(opt == 0) unionSet(a,b,++cntEdge);
		else printf("%d\n",ans=query(a,b));
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值