专题突破三之并查集Ⅰ——Portal,parity,食物链,程序自动分析,Almost Union-Find,洞穴勘测

Portal

source

百度翻译简直就是个鬼…(((m -__-)m

离线

将边和询问按权值排序,指针,将所有权值不超过当前询问 i i i的边全加进去

答案路径自然是不连通的两点 s i z siz siz之积

原本就联通的,新加边不产生贡献

#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
#define maxn 10005
#define maxm 50005
#define int long long
struct node {
    int u, v, w;
}edge[maxm];
pair < int, int > query[maxn];
int n, m, Q, cnt;
int f[maxn], siz[maxn], ans[maxn];

void makeset() {
    for( int i = 1;i <= n;i ++ ) f[i] = i, siz[i] = 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( u == v ) return;
    else {
        f[v] = u;
        cnt += siz[u] * siz[v];
        siz[u] += siz[v];
    }
}

signed main() {
    while( ~ scanf( "%lld %lld %lld", &n, &m, &Q ) ) {
    	cnt = 0;
	    for( int i = 1;i <= m;i ++ )
	        scanf( "%lld %lld %lld", &edge[i].u, &edge[i].v, &edge[i].w );
	    sort( edge + 1, edge + m + 1, []( node x, node y ) { return x.w < y.w; } );
	    for( int i = 1;i <= Q;i ++ ) {
	        scanf( "%lld", &query[i].first );
	        query[i].second = i;
	    }
	    sort( query + 1, query + Q + 1 );
	    makeset();
	    int j = 1;
	    for( int i = 1;i <= Q;i ++ ) {
	        while( j <= m && edge[j].w <= query[i].first ) 
	            merge( edge[j].u, edge[j].v ), j ++;
	        ans[query[i].second] = cnt;
	    }
	    for( int i = 1;i <= Q;i ++ )
	        printf( "%lld\n", ans[i] );
	}
    return 0;
}

parity

source

一般判断是否矛盾的并查集肯定是带权并查集

将区间 [ l , r ] [l,r] [l,r]的奇偶当作 r → l − 1 r\rightarrow l-1 rl1的权值

判断矛盾,首先 l − 1 , r l-1,r l1,r要在同一个并查集,然后看之间的权值是否与当前条件的奇偶矛盾

#include <cstdio>
#include <algorithm>
using namespace std;
#define maxn 10005
struct node {
	int l, r;
	char opt[10];
}query[maxn];
int n, m;
int x[maxn], f[maxn], sum[maxn];

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

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

int main() {
	next :
	while( scanf( "%d", &n ) && ~ n ) {
		scanf( "%d", &m );
		n = 0;
		for( int i = 1;i <= m;i ++ ) {
			scanf( "%d %d %s", &query[i].l, &query[i].r, query[i].opt );
			x[++ n] = query[i].l;
			x[++ n] = query[i].r;
		}
		sort( x + 1, x + n + 1 );
		n = unique( x + 1, x + n + 1 ) - x - 1;
		makeset();
		for( int i = 1;i <= m;i ++ ) {
			int l = lower_bound( x + 1, x + n + 1, query[i].l ) - x;
			int r = lower_bound( x + 1, x + n + 1, query[i].r ) - x;
			int t = query[i].opt[0] == 'o';
			if( l > r ) swap( l, r );
			l --;
			int u = find( l ), v = find( r );
			if( u ^ v ) {
				f[v] = u;
				sum[v] = ( -sum[r] + sum[l] + t + 2 ) % 2;
			}
			else
				if( t )
					if( sum[l] == sum[r] ) {
						printf( "%d\n", i - 1 );
						goto next;
					} else;
				else
					if( sum[l] != sum[r] ) {
						printf( "%d\n", i - 1 );
						goto next;
					} else;
		}
		printf( "%d\n", m );
	}
	return 0;
}

[NOI2001] 食物链

source

法一:建虚点

因为题目的食物链是个三元环,天然有我是 我的天敌的天敌 的天敌

i + n i+n i+n集合表示 i i i能吃的, i + 2 n i+2n i+2n表示 i i i能被吃的

大力情况枚举讨论

#include <cstdio>
#define maxn 150005
int f[maxn];
int n, m, ans;

void makeset() {
	for( int i = 1;i <= n * 3;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;
}
/*
i: itself
i+n:i can eat them
i+2*n:they can eat i
*/
int main() {
	scanf( "%d %d", &n, &m );
	makeset();
	for( int i = 1, d, x, y;i <= m;i ++ ) {
		scanf( "%d %d %d", &d, &x, &y );
		if( x > n || y > n ) ans ++;
		else
			if( d & 1 ) {
				if( find( x ) == find( y + n ) or find( x + n ) == find( y ) or find( x ) == find( y + n * 2 ) or find( x + n * 2 ) == find( y ) ) 
					ans ++;
				else 
					merge( x, y ), merge( x + n, y + n ), merge( x + n * 2, y + n * 2 );
			}
			else {
				if( x == y ) ans ++;
				else 
					if( find( x ) == find( y ) or find( x ) == find( y + n ) or find( x + n * 2 ) == find( y ) )
						ans ++;
					else
						merge( x, y + n * 2 ), merge( x + n, y ), merge( x + n * 2, y + n );
			}
	}
	printf( "%d\n", ans );
	return 0;
}

法二:带权并查集

在这里插入图片描述

不在同一个并查集的 u , v u,v u,v两点,连向自己祖先的权值为 s u m u , s u m v sum_u,sum_v sumu,sumv

如果现在将 u , v u,v u,v合并,并且是 v v v合并到 u u u,那么就是 f v → f u f_v\rightarrow f_u fvfu

权值显然为 v a l f v = − s u m v + s u m u + w val_{f_v}=-sum_v+sum_u+w valfv=sumv+sumu+w

在这里插入图片描述
路径压缩的时候,权值更新需要以前的直系父亲权值即可

回归本题,将同类看作边权为 0 0 0,吃关系看成边权为 1 1 1,在 ( m o d 3 ) \pmod 3 (mod3)意义下做

#include <cstdio>
#define maxn 50005
int n, k, ans;
int f[maxn], sum[maxn];

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

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

int main() {
	scanf( "%d %d", &n, &k );
	makeset();
	for( int i = 1, d, x, y;i <= k;i ++ ) {
		scanf( "%d %d %d", &d, &x, &y );
		if( x > n || y > n ) ans ++;
		else {
			int fx = find( x ), fy = find( y );
			if( d & 1 )
				if( fx == fy && sum[x] != sum[y] ) ans ++;
				else 
					if( fx ^ fy ) f[fx] = fy, sum[fx] = ( -sum[x] + sum[y] + 3 ) % 3;
					else;
			else {
				if( x == y ) ans ++;
				else 
					if( fx == fy )
						if( ( sum[x] - sum[y] + 3 ) % 3 != 1 ) ans ++;
						else;
					else f[fx] = fy, sum[fx] = ( -sum[x] + sum[y] + 4 ) % 3;
			}
		}
	}
	printf( "%d\n", ans );
	return 0;
}

程序自动分析

source

离散,先一股脑把所有相等的并查集到一起,最后判断不等的两个数是否在同一个集合即可

luogu上面好几篇都是错的,对拍直接挂掉,这用脚造的数据诶

#include <cstdio>
#include <algorithm>
using namespace std;
#define maxn 4000005
struct node {
	int u, v, e;
}lim[maxn];
int T, n;
int f[maxn], x[maxn];

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

int find( int x ) {
	return f[x] == 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", &T );
	next :
	while( T -- ) {
		scanf( "%d", &n );
		makeset();
		int cnt = 0;
		for( int i = 1;i <= n;i ++ ) {
			scanf( "%d %d %d", &lim[i].u, &lim[i].v, &lim[i].e );
			x[++ cnt] = lim[i].u, x[++ cnt] = lim[i].v;
		}
		sort( x + 1, x + cnt + 1 );
		cnt = unique( x + 1, x + cnt + 1 ) - x - 1;
		for( int i = 1;i <= n;i ++ ) {
			int u = lim[i].u, v = lim[i].v, e = lim[i].e;
			u = lower_bound( x + 1, x + cnt + 1, u ) - x;
			v = lower_bound( x + 1, x + cnt + 1, v ) - x;
			if( e ) merge( u, v );
		}
		for( int i = 1;i <= n;i ++ ) {
			int u = lim[i].u, v = lim[i].v, e = lim[i].e;
			u = lower_bound( x + 1, x + cnt + 1, u ) - x;
			v = lower_bound( x + 1, x + cnt + 1, v ) - x;
			if( ! e && find( u ) == find( v ) ) {
				printf( "NO\n" );
				goto next;
            }
		}
		printf( "YES\n" );
	}
	return 0;
}

UVA11987 Almost Union-Find

source

可删除并查集

建虚点

相当于套个盒子,然后父子关系就是盒子上面的互相指代,但是数就可以不存在(盒子存在)

在这里插入图片描述

#include <cstdio>
#define maxn 200005
int n, m;
int f[maxn], sum[maxn], siz[maxn];

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

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

int main() {
	while( ~ scanf( "%d %d", &n, &m ) ) {
		makeset();
		for( int i = 1, opt, p, q, u, v;i <= m;i ++ ) {
			scanf( "%d %d", &opt, &p );
			switch ( opt ) {
				case 1 : {
					scanf( "%d", &q );
					u = find( p ), v = find( q );
					if( u == v ) continue; else;
					f[v] = u, siz[u] += siz[v], sum[u] += sum[v];
					siz[v] = sum[v] = 0;
					break;
				}
				case 2 : {
					scanf( "%d", &q );
					u = find( p ), v = find( q );
					if( u == v ) continue; else;
					f[p] = v;
					sum[v] += p, sum[u] -= p;
					siz[u] --, siz[v] ++;
					break;
				}
				case 3 : {
					u = find( p );
					printf( "%d %d\n", siz[u], sum[u] );
					break;
				}
			}
		}
	}
	return 0;
}

[SDOI2008] 洞穴勘测

source

线段树+可撤销并查集

每条边存在的时间是一个区间,丢在线段树上,最多 log ⁡ n \log n logn个标记

然后dfs遍历到每个叶子节点,判断这个 i i i是否有询问挂在上面,再判断此刻已有边中两点是否联通

第一次经过点 n u m \rm num num,就把挂在上面的边操作加入并查集,先后顺序记录操作的边

dfs回溯再次经过该点的时候,就倒着把这些边的操作反着做,从而起到抵消边的效果

#include <map>
#include <cstdio>
#include <vector>
#include <iostream>
using namespace std;
#define maxn 200005
#define Pair pair < int, int >
struct node {
	int u, v, w;
	node(){}
	node( int U, int V, int W ) {
		u = U, v = V, w = W;
	}
}query[maxn];
vector < Pair > E[maxn << 2];
map < Pair, int > mp;
int n, m, top;
int f[maxn], siz[maxn], ans[maxn];
Pair sta[maxn];

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

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

void insert( int num, int l, int r, int L, int R, Pair t ) {
	if( r < L or R < l ) return;
	if( L <= l and r <= R ) {
		E[num].push_back( t );
		return;
	}
	int mid = ( l + r ) >> 1;
	insert( num << 1, l, mid, L, R, t );
	insert( num << 1 | 1, mid + 1, r, L, R, t );
}

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

void Delete( int last ) {
	while( top > last ) {
		Pair t = sta[top --];
		siz[t.second] --;
		siz[t.first] -= siz[t.second];
		f[t.second] = t.second;
	}
}

void dfs( int num, int l, int r ) {
	int now = top;
	for( auto edge : E[num] )
		merge( edge.first, edge.second );
	if( l == r ) {
		if( query[l].w ) {
			if( find( query[l].u ) == find( query[l].v ) ) 
				ans[l] = 1;
			else
				ans[l] = -1;
		}
	}
	else {
		int mid = ( l + r ) >> 1;
		dfs( num << 1, l, mid );
		dfs( num << 1 | 1, mid + 1, r );
	}
	Delete( now );
}

int main() {
	scanf( "%d %d", &n, &m );
	makeset();
	char opt[10]; int u, v;
	for( int i = 1;i <= m;i ++ ) {
		scanf( "%s %d %d", opt, &u, &v );
		if( u > v ) swap( u, v );
		Pair t = make_pair( u, v );
		switch ( opt[0] ) {
			case 'C' : {
				mp[t] = i;
				break;
			}
			case 'D' : {
				insert( 1, 1, m, mp[t], i, t );
				mp.erase( t );
				break;
			}
			case 'Q' : {
				query[i] = node( u, v, 1 );
				break;
			}
		}
	}
	for( map < Pair, int > :: iterator it = mp.begin();it != mp.end();it ++ )
		insert( 1, 1, m, it -> second, m, it -> first );
	dfs( 1, 1, m );
	for( int i = 1;i <= m;i ++ )
		if( ! ans[i] ) continue;
		else puts( ans[i] > 0 ? "Yes" : "No" );
	return 0;
}
#include <cstdio>
#include <iostream>
using namespace std;
#define maxn 300005
#define LL long long
struct node {
	int f, flag, son[2], sum, val;
}tree[maxn];
int n, m;
int st[maxn];

void reverse ( int x ) {
	swap ( tree[x].son[0], tree[x].son[1] );
	tree[x].flag ^= 1;
}

void update ( int x ) {
	tree[x].sum = tree[tree[x].son[0]].sum + tree[tree[x].son[1]].sum + tree[x].val;
}

void pushdown ( int x ) {
	if ( tree[x].flag ) {
		if ( tree[x].son[0] )
			reverse ( tree[x].son[0] );
		if ( tree[x].son[1] )
			reverse ( tree[x].son[1] );
		tree[x].flag = 0;
	}
}

bool isroot ( int x ) {
	return tree[tree[x].f].son[0] == x || tree[tree[x].f].son[1] == x;
}

void rotate ( int x ) { 
	int fa = tree[x].f; 
	int Gfa = tree[fa].f;
	int k = ( tree[fa].son[1] == x );
	if ( isroot ( fa ) )
		tree[Gfa].son[tree[Gfa].son[1] == fa] = x;
	tree[x].f = Gfa; 
	tree[fa].son[k] = tree[x].son[k ^ 1];
	if ( tree[x].son[k ^ 1] )
		tree[tree[x].son[k ^ 1]].f = fa;
	tree[x].son[k ^ 1] = fa;
	tree[fa].f = x;
	update ( fa );
	update ( x );
}

void splay ( int x ) {
	int Top = 0, y = x;
	st[++ Top] = y;
	while ( isroot ( y ) )
		st[++ Top] = y = tree[y].f;
	while ( Top )
		pushdown ( st[Top -- ] );
	while ( isroot ( x ) ) {
		int fa = tree[x].f, Gfa = tree[fa].f;
		if ( isroot ( fa ) )
			( ( tree[Gfa].son[0] == fa ) ^ ( tree[fa].son[0] == x ) ) ? rotate ( x ) : rotate ( fa );
		rotate ( x );
	}
}

void access ( int x ) {
	for ( int son = 0;x;son = x, x = tree[x].f ) {
		splay ( x );
		tree[x].son[1] = son;
		update ( x );
	}
}

void MakeRoot ( int x ) {
	access ( x );
	splay ( x );
	reverse ( x );
}

int FindRoot ( int x ) {
	access ( x );
	splay ( x );
	while ( tree[x].son[0] ) {
		pushdown ( x );
		x = tree[x].son[0];
	}
	splay ( x );
	return x;
}

void split ( int x, int y ) { 
	MakeRoot ( x );
	access ( y );
	splay ( y );
}

bool link ( int x, int y ) {
	MakeRoot ( x );
	if ( FindRoot ( y ) == x )
		return 0;
	tree[x].f = y;
	return 1;
}

void cut ( int x, int y ) { 
	MakeRoot ( x );
	if ( FindRoot ( y ) != x || tree[y].f != x || tree[y].son[0] )
		return;
	tree[y].f = tree[x].son[1] = 0;
	update ( x );
}

int main() {
	scanf ( "%d %d", &n, &m );
	int x, y;
	char opt[15];
	for ( int i = 1;i <= m;i ++ ) {
		scanf ( "%s %d %d", &opt, &x, &y );
		switch ( opt[0] ) {
			case 'Q' : {
				MakeRoot ( x );
				if ( FindRoot ( y ) == x )
					printf ( "Yes\n" );
				else
					printf ( "No\n" );
				break;
			}
			case 'C' : link ( x, y ); break;
			case 'D' : cut ( x, y ); break;
		}
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值