【CodeForces】445C Civilization 并查集

本文详细解析了一道CodeForces竞赛题目的解题思路,包括如何使用并查集预处理连通块,如何求解每个连通块的最长路径,以及如何通过输入优化提高代码效率。最后分享了作者的解题心得,包括如何巧妙地运用公式和算法优化,最终实现快速解答。

传送门:【CodeForces】445C Civilization


题目分析:要求每次合并后的连通块内的最长路径最短,那么首先我们要得到合并前的连通块各自内部最长路径的长度L1、L2,然后新的连通块的最长路径一定是max{max{L1,L2},(L1+1)/2+(L2+1)/2+1}。

为什么?合并时取两个连通块最长路径处在最中间的端点合并一定是最优的,合并后的路径一定不会变差。

那么怎么得到一开始所有连通块的长度?首先我们要注意,因为题目一开始就给了固定的几条边,所以一开始所有连通块内部的最长路径是固定长度的!

我们先用并查集预处理出一开始的连通块,同时建立无向边,添加一条边能变成环的不加也一样。处理完以后,从所有连通块的根结点开始做一次dfs,找到离根最远的点,再用这个点做一次dfs,就能找到这个连通块的最长路径了,将路径长度保存在根结点即可。

接下来的步骤就是利用上面的公式就好了。

看到别人用了输入优化跑的比我快,很不开心,也上了输入优化,速度飞快~不小心就跑到第一去了害羞



代码如下:


#include <cmath>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;

#define REP( i , a , b ) for ( int i = a ; i < b ; ++ i )
#define FOR( i , a , b ) for ( int i = a ; i <= b ; ++ i )
#define REV( i , a , b ) for ( int i = a ; i >= b ; -- i )
#define CLR( a , x ) memset ( a , x , sizeof a )

const int MAXN = 300005 ;
const int MAXE = 600005 ;

struct Edge {
	int v , n ;
	Edge () {}
	Edge ( int v , int n ) : v ( v ) , n ( n ) {}
} E[MAXE] ;

int H[MAXN] , cntE ;
int p[MAXN] ;
int X , length ;
int L[MAXN] ;
int n , m , q ;

int find ( int x ) {
	return !p[x] ? x : ( p[x] = find ( p[x] ) ) ;
}

void addedge ( int u , int v ) {
	E[cntE] = Edge ( v , H[u] ) ;
	H[u] = cntE ++ ;
	E[cntE] = Edge ( u , H[v] ) ;
	H[v] = cntE ++ ;
}

void dfs ( int u , int fa = -1 , int d = 0 ) {
	if ( length < d ) {
		length = d ;
		X = u ;
	}
	for ( int i = H[u] ; i ; i = E[i].n )
		if ( E[i].v != fa )
			dfs ( E[i].v , u , d + 1 ) ;
}

void read ( int &x ) {
	char c ;
	while ( ( c = getchar () ) < '0' || c > '9' ) ;
	x = c - '0' ;
	while ( ( c = getchar () ) >= '0' && c <= '9' )
		x = x * 10 + c - '0' ;
}

void solve () {
	int ch , x , y , u , v ;
	cntE = 1 ;
	//CLR ( H , 0 ) ;
	//CLR ( p , 0 ) ;
	//CLR ( L , 0 ) ;
	REP ( i , 0 , m ) {
		read ( u ) , read ( v ) ;
		x = find ( u ) ;
		y = find ( v ) ;
		if ( x != y ) {
			p[x] = y ;
			addedge ( u , v ) ;
		}
	}
	FOR ( i , 1 , n )
		if ( !p[i] ) {
			length = -1 ;
			dfs ( i ) ;
			length = -1 ;
			dfs ( X ) ;
			L[i] = length ;
		}
	REP ( i , 0 , q ) {
		read ( ch ) , read ( x ) ;
		if ( ch == 1 )
			printf ( "%d\n" , L[find ( x )] ) ;
		else {
			read ( y ) ;
			x = find ( x ) ;
			y = find ( y ) ;
			if ( x != y ) {
				p[x] = y ;
				L[y] = max ( max ( L[x] , L[y] ) , ( L[x] + 1 ) / 2 + ( L[y] + 1 ) / 2 + 1 ) ;
			}
		}
	}
}

int main () {
	while ( ~scanf ( "%d%d%d" , &n , &m , &q ) )
		solve () ;
	return 0 ;
}


### 关于 CodeForces 892E 的解题思路分析 #### 使用可撤销并查集解决最小生成树中的边集合验证问题 针对给定的无向图以及多个询问,每个询问涉及一组特定的边,并要求判断这组边能否同时存在于某棵最小生成树中。此问题可以通过结合Kruskal算法构建最小生成树的过程来求解,在这一过程中利用到的是按照权重升序排列后的边逐步加入至森林结构之中[^1]。 为了高效处理多次查询而不影响后续操作的结果,引入了带有回溯功能的数据结构——即所谓的“可撤销并查集”。这种特殊形式的并查集允许执行合并(union)的同时记录下每一次变动以便之后能够恢复原状;当完成一次查询判定后即可通过一系列反向动作使数据结构回到初始状态,从而不影响其他独立事件的发生逻辑[^3]。 具体实现方法如下: - 将所有的边依据其权重从小到大排序; - 对每一个询问所涉及到的边也做同样的预处理; - 开始遍历已排序好的全局边列表,每当遇到属于当前待检验询问范围内的边时,则尝试将其纳入现有连通分量内; - 如果发现形成环路则说明该询问无法满足条件; - 同样地,任何不属于当前询问但同样处于相同权值下的其它边也应该被考虑进来以确保最终形成的MST是最优解的一部分; - 完成一轮测试后记得清除所有临时更改使得系统重置为未受干扰的状态准备迎接下一个挑战。 ```cpp #include <bits/stdc++.h> using namespace std; struct Edge { int u, v; }; class DSUWithRollback { public: vector<int> parent, rank, historyParent, historyRank; void init(int n){ parent.resize(n); iota(parent.begin(), parent.end(), 0); // Fill with identity mapping. rank.assign(n, 0); historyParent.clear(); historyRank.clear(); } int findSet(int i) {return (parent[i]==i)?i:(findSet(parent[i]));} bool isSameSet(int i, int j){ return findSet(i)==findSet(j);} void unionSets(int i, int j){ if (!isSameSet(i,j)){ historyParent.push_back(findSet(i)); historyParent.push_back(findSet(j)); historyRank.push_back(rank[findSet(i)]); historyRank.push_back(rank[findSet(j)]); int x=findSet(i), y=findSet(j); if (rank[x]>rank[y]) swap(x,y); parent[x]=y; if (rank[x]==rank[y]) ++rank[y]; } } void rollback(){ while(!historyParent.empty()){ parent[historyParent.back()]=historyParent.back(); historyParent.pop_back(); rank[historyParent.back()] = historyRank.back(); historyParent.pop_back(); historyRank.pop_back(); } } }; ``` 上述代码展示了如何创建一个支持撤销机制的并查集类`DSUWithRollback`,它可以在不破坏原有连接关系的前提下安全地进行节点间的联合与查找操作。此外还提供了用于追踪变化历史的方法,方便在必要时候撤消最近的一系列更动。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值