永无乡

luoguP3224 [HNOI2012]永无乡

题目大意:

给定一个不连通的图,每个点有点权,每次操作分为两种,一是让其中两个点加一条边,二是询问某一个联通块中第 \(k\) 小的权值。

解法1:

因为要求第 \(k\) 小的权值,所以我们考虑对每一个点开一颗平衡树,当我们加边的时候就是将两棵平衡树合并,而询问第 \(k\) 小就是平衡树的基本操作了。

考虑如何将两棵平衡树进行合并,暴力合并肯定不行。考虑启发式合并是否适用,就是把其中小的一棵树拿出来,枚举每个点,把他加入大的那棵平衡树,这样子我们对每个点最多只会加 \(\log n\) 次,因为我们是让小的加入大的,所以我们每加一次,对于小的那棵树来说就相当于这棵小的树的大小加了一倍多,但是大的那棵树我们不动他,所以这样子的话我们每个点最多加到 \(\log n\) 次就会和 \(n\) 一样大了,所以均摊下来复杂度是 \(n\log n\) 的,复杂度正确。

接下来就是代码,我用的是fhq-treap。

Code:

#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <string>
#include <ctime>
using namespace std ;

#define int long long
int n , m , q , cnt , a[100005] , root[100005] ;
int clr[100005] , t[100005][2] , s[100005] , c[100005] , v[100005] , d[100005] ;
string na ;

int randoom ( )
{
	return ( ( rand ( ) << 5 ) & rand ( ) ) | rand ( ) ;
}

int find ( int x )
{
	if ( x == clr [ x ] )
		return x ;
	return clr [ x ] = find ( clr [ x ] ) ;
}

int newnode ( int x , int y )
{
	c [ ++ cnt ] = randoom ( ) ;
	s [ cnt ] = 1 ;
	v [ cnt ] = x ;
	d [ cnt ] = y ;
	return cnt ;
}

void pushup ( int now )
{
	s [ now ] = s [ t [ now ] [ 0 ] ] + s [ t [ now ] [ 1 ] ] + 1 ;
}

void split ( int now , int k , int &x , int &y )
{
	if ( !now ) { x = y = 0 ; return ; }
	if ( v [ now ] <= k )
		x = now , split ( t [ now ] [ 1 ] , k , t [ now ] [ 1 ] , y ) ;
	else
		y = now , split ( t [ now ] [ 0 ] , k , x , t [ now ] [ 0 ] ) ;
	pushup ( now ) ;
}

int merge ( int x , int y )
{
	if ( !x || !y )
		return x + y ;
	if ( c [ x ] < c [ y ] )
	{
		t [ x ] [ 1 ] = merge ( t [ x ] [ 1 ] , y ) ;
		pushup ( x ) ;
		return x ;
	}
	else
	{
		t [ y ] [ 0 ] = merge ( x , t [ y ] [ 0 ] ) ;
		pushup ( y ) ;
		return y ;
	}
}

void dfs ( int &x , int y )
{
	if ( !y )
		return ;
	dfs ( x , t [ y ] [ 0 ] ) ;
	dfs ( x , t [ y ] [ 1 ] ) ;
	
	t [ y ] [ 0 ] = t [ y ] [ 1 ] = 0 ;
	int u = 0 , w = 0 ;
	split ( x , v [ y ] , u , w ) ;
	x = merge ( merge ( u , y ) , w ) ;
}

int kth ( int now , int k )
{
	if ( k <= s [ t [ now ] [ 0 ] ] )
		return kth ( t [ now ] [ 0 ] , k ) ;
	else if ( k == s [ t [ now ] [ 0 ] ] + 1 )
		return now ;
	return kth ( t [ now ] [ 1 ] , k - s [ t [ now ] [ 0 ] ] - 1 ) ;
}

signed main ( )
{
//	srand ( time ( NULL ) ) ;
	cin >> n >> m ;
	for ( int i = 1 ; i <= n ; ++ i )
	{
		cin >> a [ i ] ;
		clr [ i ] = i ;
		root [ i ] = newnode ( a [ i ] , i ) ;
	}
	for ( int i = 1 ; i <= m ; ++ i )
	{
		int u , v ;
		cin >> u >> v ;
		int x = find ( u ) , y = find ( v ) ;
		if ( x != y )
		{
			if ( s [ root [ x ] ] < s [ root [ y ] ] )
				swap ( x , y ) ;
			clr [ y ] = x ;
			dfs ( root [ x ] , root [ y ] ) ;
		}
	}
	cin >> q ;
	for ( int i = 1 ; i <= q ; ++ i )
	{
		int x , y , u = 0 , z = 0 , w = 0 ;
		cin >> na >> x >> y ;
		if ( na [ 0 ] == 'Q' )
		{
			x = find ( x ) ;
			if ( s [ root [ x ] ] < y )
			{
				cout << -1 << endl ;
				continue ;
			}
			u = kth ( root [ x ] , y ) ;
			cout << d [ u ] << endl ;
		}
		else
		{
			x = find ( x ) ;
			y = find ( y ) ;
			if ( x != y )
			{
				if ( s [ root [ x ] ] < s [ root [ y ] ] )
					swap ( x , y ) ;
				clr [ y ] = x ;
				dfs ( root [ x ] , root [ y ] ) ;
			}
		}
	}
	return 0 ;
}

解法2:

上面那篇用的是平衡树,其实也可以用线段树合并加并查集加主席树做,我们开始的时候对每个节点开一课主席树,并且每个点代表一个并查集。

然后当我们进行合并操作的时候,就把两个点代表的并查集的主席树合并起来,并且把并查集也合并。

查询的时候就是在主席树上查询第 \(k\) 大的数,这是主席树的基本操作了。

Code:

#include <iostream>
using namespace std ;

const int N = 2000005 ;
int n , m , dt , clr[N] , rt[N] , a[N] ;
int t[N<<2] , ls[N<<2] , rs[N<<2] , id[N<<2] ;
char op[4] ;

void change ( int &k , int l , int r , int x , int y )
{
	if ( ! k )
		k = ++ dt ;
	if ( l == r )
	{
		t [ k ] = 1 ;
		id [ k ] = y ;
		return ;
	}
	int mid = ( l + r ) >> 1 ;
	if ( x <= mid )
		change ( ls [ k ] , l , mid , x , y ) ;
	else
		change ( rs [ k ] , mid + 1 , r , x , y ) ;
	t [ k ] = t [ ls [ k ] ] + t [ rs [ k ] ] ;
}

int find ( int x )
{
	if ( clr [ x ] == x )
		return x ;
	return clr [ x ] = find ( clr [ x ] ) ;
}

int merge ( int x , int y , int l , int r )
{
	if ( ! x || ! y )
		return x | y ;
	if ( l == r )
	{
		t [ x ] += t [ y ] ;
		return x ;
	}
	t [ x ] += t [ y ] ;
	int mid = ( l + r ) >> 1 ;
	ls [ x ] = merge ( ls [ x ] , ls [ y ] , l , mid ) ;
	rs [ x ] = merge ( rs [ x ] , rs [ y ] , mid + 1 , r ) ;
	return x ;
}

int query ( int k , int l , int r , int x )
{
	if ( l == r )
		return id [ k ] ;
	int mid = ( l + r ) >> 1 ;
	if ( t [ ls [ k ] ] >= x )
		return query ( ls [ k ] , l , mid , x ) ;
	return query ( rs [ k ] , mid + 1 , r , x - t [ ls [ k ] ] ) ;
}

int main ( )
{
	cin >> n >> m ;
	for ( int i = 1 ; i <= n ; ++ i )
	{
		cin >> a [ i ] ;
		change ( rt [ i ] , 1 , n , a [ i ] , i ) ;
		clr [ i ] = i ;
	}
	for ( int i = 1 ; i <= m ; ++ i )
	{
		int u , v ;
		cin >> u >> v ;
		u = find ( u ) , v = find ( v ) ;
		if ( u == v )
			continue ;
		clr [ u ] = v ;
		rt [ v ] = merge ( rt [ u ] , rt [ v ] , 1 , n ) ;
	}
	cin >> m ;
	for ( int i = 1 ; i <= m ; ++ i )
	{
		int u , v ;
		cin >> op >> u >> v ;
		if ( op [ 0 ] == 'B' )
		{
			u = find ( u ) ;
			v = find ( v ) ;
			if ( u == v )
				continue ;
			clr [ u ] = v ;
			rt [ v ] = merge ( rt [ u ] , rt [ v ] , 1 , n ) ;
		}
		else
		{
			u = find ( u ) ;
			if ( t [ rt [ u ] ] < v )
			{
				cout << "-1\n" ;
				continue ;
			}
			cout << query ( rt [ u ] , 1 , n , v ) << '\n' ;
		}
	}
	return 0 ;
}
posted @ 2021-10-09 22:50  灵华  阅读(65)  评论(0)    收藏  举报