专题突破三之并查集Ⅱ——星球大战,In Touch,方格染色,Junk-Mail Filter,关押罪犯,Silver Woods,Must Be Rectangular!

本文介绍了多种算法竞赛中并查集的应用,包括星球大战问题的正逆操作、最短路问题的优化、方格染色的约束关系处理、垃圾邮件过滤的集合操作以及矩形区域的构建等。通过实例解析并查集在解决实际问题中的灵活运用,展示了并查集在处理连通性问题上的高效性。
摘要由CSDN通过智能技术生成

[JSOI2008]星球大战

source

非常套路的,正着打击星球,逆着就是添加星球以及关系,并查集维护此时连通块个数

就是这个星球被打击前的答案

#include <cstdio>
#include <vector>
#include <iostream>
using namespace std;
#define maxn 400005
vector < int > h[maxn];
pair < int, int > p[maxn];
int n, m, k, ans;
int f[maxn], g[maxn], ret[maxn];
bool used[maxn], vis[maxn];

void makeset() {
	for( int i = 0;i < n;i ++ ) f[i] = i;
}

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

void merge( int u, int v ) {
	u = find( u ), v = find( v );
	if( u ^ v ) ans --, f[v] = u;
}

int main() {
	scanf( "%d %d", &n, &m );
	for( int i = 1;i <= m;i ++ ) {
		scanf( "%d %d", &p[i].first, &p[i].second );
		h[p[i].first].push_back( i );
		h[p[i].second].push_back( i );
	}
	scanf( "%d", &k );
	makeset();
	for( int i = 1;i <= k;i ++ )
		scanf( "%d", &g[i] ), used[g[i]] = 1;
	ans = n - k;
	for( int i = 1;i <= m;i ++ )
		if( ! used[p[i].first] and ! used[p[i].second] )
			merge( p[i].first, p[i].second );
	for( int i = k;i;i -- ) {
		ret[i] = ans;
		ans ++;
		used[g[i]] = 0;
		for( auto t : h[g[i]] )
			if( vis[t] ) continue;
			else if( used[p[t].first] or used[p[t].second] )
				continue;
			else merge( p[t].first, p[t].second );
	}
	printf( "%d\n", ans );
	for( int i = 1;i <= k;i ++ )
		printf( "%d\n", ret[i] );
	return 0;
}

In Touch

source

转化成最短路问题,套用 d i j k s t r a \rm dijkstra dijkstra,每个点都只访问一次,但是范围那么大,用并查集帮助跳过已访问点,直指新点

#include <iostream>
#include <cstdio>
#include <queue>
using namespace std;
#define Pair pair < int, int >
#define int long long
#define maxn 200005
#define inf 1e15
priority_queue < Pair, vector < Pair >, greater < Pair > > q;
int T, n;
int dis[maxn], f[maxn], L[maxn], R[maxn], c[maxn];
int MS[2] = { -1, 1 };

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

signed main() {
	scanf( "%lld", &T );
	while( T -- ) {
		scanf( "%lld", &n );
		for( int i = 1;i <= n;i ++ ) scanf( "%lld", &L[i] );
		for( int i = 1;i <= n;i ++ ) scanf( "%lld", &R[i] );
		for( int i = 1;i <= n;i ++ ) scanf( "%lld", &c[i] );
		for( int i = 0;i <= n + 1;i ++ ) f[i] = i, dis[i] = inf;
		q.push( make_pair( dis[1] = c[1], 1 ) );
		while( ! q.empty() ) {
			int now = q.top().second; q.pop();
			for( int k = 0;k < 2;k ++ ) {
				int l = now + L[now] * MS[k];
				int r = now + R[now] * MS[k];
				if( l > r ) swap( l, r );
				l = min( l, n + 1 );
				l = max( l, 1ll );
				if( l > r ) continue;
				for( int nxt = l;;nxt ++ ) {
					nxt = find( nxt );
					if( nxt <= 0 || nxt > n || nxt > r ) break;
					if( dis[nxt] > dis[now] + c[nxt] ) {
						dis[nxt] = dis[now] + c[nxt];
						q.push( make_pair( dis[nxt], nxt ) );
					}
					f[find( nxt )] = find( nxt + 1 );
				}
			}
		}
		printf( "0" );
		for( int i = 2;i <= n;i ++ )
			if( dis[i] == inf ) printf( " -1" );
			else printf( " %lld", dis[i] - c[i] );
		printf( "\n" );
	}
	return 0;
}

方格染色

source

observation1: 只要确定了第一行和第一列,整张表格就被确定了

observation2: g 1 , 1 ⨁ g i , 1 ⨁ g 1 , j ⨁ g i , j = [ i   m o d   2 = 0   a n d   j   m o d   2 = 0 ] g_{1,1}\bigoplus g_{i,1}\bigoplus g_{1,j}\bigoplus g_{i,j}=[i\ \rm mod\ 2=0\ and\ j\ mod\ 2=0] g1,1gi,1g1,jgi,j=[i mod 2=0 and j mod 2=0]

可以通过枚举 g 1 , 1 g_{1,1} g1,1的状态,通过已知的 g i , j g_{i,j} gi,j来判断 g i , 1 , g 1 , j g_{i,1},g_{1,j} gi,1,g1,j的关系

这样形成了若干组有且仅有两个选项的约束关系,带权并查集维护

连通块内有一个取值定了,其他的取值也定了

如果有解,答案就是2^(连通块数量-1),减1是 g 1 , 1 g_{1,1} g1,1的状态因为是枚举的,算已知的

#include <cstdio>
#define int long long
#define mod 1000000000
#define maxn 2000005
int n, m, k;
bool flag0, flag1;
int x[maxn], y[maxn], c[maxn], f[maxn], w[maxn];

int qkpow( int x, int y ) {
	int ans = 1;
	while( y ) {
		if( y & 1 ) ans = ans * x % mod;
		x = x * x % mod;
		y >>= 1;
	}
	return ans;
}

int find( int x ) {
	if( x == f[x] ) return x;
	else {
		int fa = f[x];
		f[x] = find( f[x] );
		w[x] = ( w[x] + w[fa] ) % 2;
		return f[x];
	}
}

int solve() {
	for( int i = 1;i <= n + m;i ++ ) f[i] = i, w[i] = 0;
	f[n + 1] = 1;
	for( int i = 1;i <= k;i ++ ) {
		if( x[i] == 1 && y[i] == 1 ) continue;
		int u = find( x[i] ), v = find( y[i] + n );
		int t = w[x[i]] ^ w[y[i] + n] ^ c[i] ^ ( x[i] & 1 or y[i] & 1 ) ^ 1;
		if( u == v and t ) return 0;
		f[v] = u, w[v] = t;
	} 
	int cnt = 0;
	for( int i = 1;i <= n + m;i ++ )
		if( find( i ) == i ) cnt ++;
	return qkpow( 2, cnt - 1 );
}

signed main() {
	scanf( "%lld %lld %lld", &n, &m, &k );
	for( int i = 1;i <= k;i ++ ) {
		scanf( "%lld %lld %lld", &x[i], &y[i], &c[i] );
		if( x[i] == 1 && y[i] == 1 )
			if( ! c[i] ) flag0 = 1;
			else flag1 = 1;
		else;
	}
	int ans0 = solve();//c[g[1][1]]=0 blue
	for( int i = 1;i <= k;i ++ ) c[i] ^= 1;//c[g[1][1]]=1 red
	int ans1 = solve();
	if( flag0 ) ans1 = 0;
	if( flag1 ) ans0 = 0;
	printf( "%lld\n", ( ans0 + ans1 ) % mod );
	return 0;
}

Junk-Mail Filter

source

可删除并查集模板

只需要注意一下被删除前的集合若只有一个,元素被删除后集合也就不存在了

#include <cstdio>
#include <iostream>
using namespace std;
#define maxn 2200000
int n, m, ans, cnt;
int f[maxn], siz[maxn];

void makeset() {
	cnt = n;
	for( int i = 0;i < n;i ++ )
		f[i] = f[i + n] = cnt ++, siz[i + n] = 1;
}

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

void merge( int u, int v ) {
	u = find( u ), v = find( v );
	if( siz[u] < siz[v] ) swap( u, v );
	if( u ^ v )
		siz[u] += siz[v], f[v] = u, ans --;
}

void Delete( int x ) {
	int fx = find( x );
	siz[fx] --;
	if( ! siz[fx] ) ans --;
	f[x] = f[cnt] = cnt;
	siz[cnt] = 1;
	cnt ++;
	ans ++;
}

int main() {
	int T = 0;
	while( scanf( "%d %d", &n, &m ) ) {
		if( ! n and ! m ) return 0;
		makeset();
		ans = n;
		char opt[5]; int x, y;
		for( int i = 1;i <= m;i ++ ) {
			scanf( "%s", opt );
			if( opt[0] == 'M' ) {
				scanf( "%d %d", &x, &y );
				merge( x, y );
			}
			else {
				scanf( "%d", &x );
				Delete( x );
			}
		}
		printf( "Case #%d: %d\n", ++ T, ans );
	}
	return 0;
}

[NOIP2010 提高组] 关押罪犯

source

贪心,先把冲突最大的安排,可以选择拆点 i , i + n i,i+n i,i+n,两个人不同合并 ( i , j + n ) / ( i + n , j ) (i,j+n)/(i+n,j) (i,j+n)/(i+n,j)

也可以选择带权并查集在 % 2 \% 2 %2意义下做

#include <cstdio>
#include <algorithm>
using namespace std;
#define maxn 100005
struct node {
	int u, v, w;
	node(){}
	node( int U, int V, int W ) {
		u = U, v = V, w = W;
	}
}r[maxn];
int n, m;
int f[maxn];

void makeset() {
	for( int i = 1;i <= ( n << 1 );i ++ ) f[i] = i;
}

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

void merge( int u, int v ) {
	u = find( u ), v = find( v ), f[v] = u;
}

int main() {
	scanf( "%d %d", &n, &m );
	for( int i = 1, u, v, w;i <= m;i ++ ) {
		scanf( "%d %d %d", &u, &v, &w );
		r[i] = node( u, v, w );
	}
	makeset();
	sort( r + 1, r + m + 1, []( node x, node y ) { return x.w > y.w; } );
	for( int i = 1;i <= m;i ++ ) {
		int u = r[i].u, v = r[i].v;
		if( find( u ) == find( v ) )
			return ! printf( "%d\n", r[i].w );
		else
			merge( u, v + n ), merge( v, u + n );
	}
	printf( "0\n" );
	return 0;
}

Silver Woods

source

跟一道奶酪题相似,奶酪是空心球,老鼠从底到顶;这道题是从左到右

二分半径,然后将圆卡不过的连接起来

具体而言就是点点之间距离小于直径,点和上下界面距离小于直径,合并起来

并查集判断上界面和下界面是否相通,不通证明有一种方法圆可以通过

#include <cstdio>
#include <cmath>
#define eps 1e-6
#define maxn 105
int n;
double x[maxn], y[maxn];
int f[maxn];

void makeset() {
	for( int i = 0;i <= n + 1;i ++ ) f[i] = i;
}

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

void merge( int u, int v ) {
	u = find( u ), v = find( v ), f[v] = u;
}

double dis( int i, int j ) {
	return sqrt( ( x[i] - x[j] ) * ( x[i] - x[j] ) + ( y[i] - y[j] ) * ( y[i] - y[j] ) );
}

bool check( double r ) {
	double d = r * 2;
	makeset();
	for( int i = 1;i <= n;i ++ ) {
		if( y[i] + d >= 100 )
			merge( 0, i );
		if( y[i] - d <= -100 )
			merge( n + 1, i );
		for( int j = i + 1;j <= n;j ++ )
			if( dis( i, j ) < d )
				merge( i, j );
	}
	if( find( 0 ) == find( n + 1 ) ) return 0;
	else return 1;
}

int main() {
	scanf( "%d", &n );
	for( int i = 1;i <= n;i ++ )
		scanf( "%lf %lf", &x[i], &y[i] );
	double l = 0, r = 100, ans;
	while( r - l > eps ) {
		double mid = ( l + r ) / 2;
		if( check( mid ) ) ans = mid, l = mid;
		else r = mid;
	}
	printf( "%.6f\n", ans );
	return 0;
}

Must Be Rectangular!

source

img

对于每个坐标 ( x i , y i ) (xi,yi) (xi,yi) x i xi xi y i + n yi+n yi+n连边,形成一个二分图

然后后续添加点实际上就是对这个二分图的每个分量补成完全二分图

并查集维护一下二分图的边的个数,两个部的点的个数

#include <cstdio>
#define maxn 100000
int n;
int f[maxn << 2], row[maxn << 2], col[maxn << 2];

void makeset() {
	for( int i = 1;i <= ( maxn << 1 );i ++ )
		f[i] = i;
}

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

void merge( int u, int v ) {
	u = find( u ), v = find( v ), f[v] = u;
}

int main() {
	scanf( "%d", &n );
	makeset();
	for( int i = 1, x, y;i <= n;i ++ ) {
		scanf( "%d %d", &x, &y );
		merge( x, y + maxn );
	}
	long long ans = 0;
	for( int i = 1;i <= maxn;i ++ ) row[find( i )] ++;
	for( int i = 1;i <= maxn;i ++ ) col[find( i + maxn )] ++;
	for( int i = 1;i <= maxn;i ++ ) ans += 1ll * row[i] * col[i];
	printf( "%lld\n", ans - n );
	return 0;
}

{
u = find( u ), v = find( v ), f[v] = u;
}

int main() {
scanf( “%d”, &n );
makeset();
for( int i = 1, x, y;i <= n;i ++ ) {
scanf( “%d %d”, &x, &y );
merge( x, y + maxn );
}
long long ans = 0;
for( int i = 1;i <= maxn;i ++ ) row[find( i )] ++;
for( int i = 1;i <= maxn;i ++ ) col[find( i + maxn )] ++;
for( int i = 1;i <= maxn;i ++ ) ans += 1ll * row[i] * col[i];
printf( “%lld\n”, ans - n );
return 0;
}


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值