【】Star War 并查集

Star War

Time Limit : 10000/10000ms (Java/Other)   Memory Limit : 65535/65536K (Java/Other)
为了讨好孩子们,Soybean 准备给孩子们看《Star War》。。。
看完《Star War》之后,孩子们十分开心,但同样的电影看两遍就没有任何意义了。这时候孩子们听说 (他们到底是听谁说的呢?不会又是 Soybean 说的吧,接下来他又要后悔了)有一个游戏也叫 《Star War》, 孩子们十分感兴趣。当然接下来他们要求 Soybean 开发这款游戏(痛苦啊!Soybean 果然后悔了)。
经过千辛万苦,在 Soybean 和众多人的努力之下 (他当然不会一个人开发,他找了许多老同学来帮忙)游戏终于开发好了。
这是一款十分高级的游戏,它支持 Soybean 和孩子们一起玩 (话说一个孤岛上怎么玩这么高级的游戏,那里真的有这种设备吗?)。
游戏描述了发生在遥远的银河系之外的一个星系,那里存在许多星球,每一个星球都有一个战斗力。位于%#$3(这是星球坐标)的 S 星球企图称霸整个宇宙(当然会有人来阻止他的)。由于 S 星球的战斗力特别高,为了阻止 S 星球的企图,N 个星球准备形成星球联盟来对付 S 星球(形成星球联盟之后联盟的战斗力为联盟中所有星球的战斗力总和)。
当然星球联盟是有条件的,如果两个星球之间存在一条轨道(当然是双向的)的话那么就可以说这两个星球属于同一个联盟。
为了尽快抓紧形成联盟, N 个星球都在努力建设轨道。就在他们造好 M 条轨道的时候,S 星球察觉到了他们的企图,为了自己的称霸大业,他决定攻击那些轨道(至于为什么不能攻击星球呢?因为银河法规定不能随意攻击其他星球,S 星球在这种时候可不能触犯银河法)。
游戏就这样开始了。
当然 Soybean 不能让孩子们当坏蛋,于是他就担任 S 星球的指挥官,而孩子们就分别担任 N 个星球的指挥官。星球大战就要开始了。。。。。。
突然,N 个星球的指挥官都不能动了(这就意味着他们不能),孩子们大喊:这游戏有Bug,要求终止游戏。(他们怎么连 Bug 都知道了)。Soybean 他可不管,他要抓紧时间破坏轨道,鬼才知道是么时候其他指挥官又能行动了。
Soybean 他是游戏的设计者,他知道一些其他的 Bug。他可以通过一些操作来获得他想要的信息。每次 Soybean 可以进行三种操作之一:破坏轨道、询问一个星球所在联盟的战斗力或询问所有联盟中的最小战斗力。
不幸的是,这些 Bug 不能用了。由于当然你也参加了游戏的设计,并且这一部分的程序就是你设计的。为了成功获胜 Soybean 要求你重新打一遍程序,并能在 1s 内解决 Soybean的所有询问。

Input

输入第一行包含两个整数 N 和 M (1<=N<=100000, 1<=M<=200000), 分别表示星球的数目和轨道的数目;
接下来一行包含 N 个整数 P1,P2,…,PN(1<=Pi<=20000),表示第 i 个星球战斗力;
接下来 M 行,每行有两个用空格隔开整数 a,b(1<=a,b<=N,a!=b),分别表示第 i 条轨道连接星球 a 和星球 b,输入数据保证每一条轨道只会描述一次;
接下来一行包含一个整数 Q(1<=Q<=200000),表示操作的次数;
接下来 Q 行,每行会出现以下 3 种形式之一:
* Destroy a b– 星球 a 和星球 b 之间的轨道受到 Soybean 攻击并且被破坏掉,输入数据保证星球 a 和星球 b 之间的轨道存在,并且以前没有被破坏掉;
* Query a– 询问星球 a 所在的星球联盟的战斗力;
* Getmin – 询问所有联盟中战斗力最小的那个联盟的战斗力,如果存在多个联盟的战斗力满足要求,随便输出一个即可。

Output

只要求你的程序对 Query 和 Getmin 操作进行回答。对于每个操作,你的程序只要求输出单独一行,包含一个整数。

Sample Input

7 6 
1 2 3 4 5 6 7 
1 2 
1 3 
1 4 
2 3 
2 4 
3 4 
8 
Query 2 
Getmin 
Destroy 1 4 
Destroy 2 4 
Destroy 3 4 
Query 7 
Getmin 
Query 1 

Sample Output

10 
5 
7 
4 
6

Author

LX


这是同学给我找来的图论题,题目是他高中学长原创的。。Orz。。这题也写的很繁琐的样子。。。毕竟不会STL。。

题目分析:可以理解为倒着并查集,这点很容易想到,因为只有删除边的操作,反过来就是增加边了,那么很容易就可以和并查集联系起来。
首先将每个要删除的边标记一下,然后用剩下的边用并查集构成初始的连通块。对于查询一个联盟的实力,我们可以直接在操作并查集的时候完成实力的修改,所以可以在O(1)内解决。对于删除边,也就是倒着来的添加边,继续并查集,两点在同一个连通块中就跳过这次操作,否则合并并修改联盟实力的信息。对于询问最小值,其实我们可以用一个最小堆保存,那么每次也就是O(logN)的复杂度就可以解决了。
那么最小堆怎么使用?对于每个连通块保存他的编号,然后每两个连通块合并的时候,标记这两个连通块被删除,然后对于新的连通块我们给他一个新的编号并标记这个连通块是存在的。然后每次从堆中取出元素的时候,如果这个元素对应的连通块是被删除了的,那么继续取,直到遇到存在的连通块,易知这个就是要找的最小实力的连通块了。

PS:这个代码被我写的不忍直视了。。。。如果有谁有什么好的建议可以告诉我一下,谢谢~

代码如下:

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

#define REP( i , n ) for ( int i = 0 ; i < n ; ++ i )
#define REPF( i , a , b ) for ( int i = a ; i <= b ; ++ i )
#define REPV( i , a , b ) for ( int i = a ; i >= b ; -- i )
#define clear( a , x ) memset ( a , x , sizeof a )

const int MAXN = 100005 ;
const int MAXE = 200005 ;
const int MAXH = 200005 ;
const int MAXQ = 200005 ;
const int MAXM = 300005 ;

struct Line {
	int x , y , f ;
	void input () {
		scanf ( "%d%d" , &x , &y ) ;
		if ( x > y )
			swap ( x , y ) ;
		f = 1 ;
	}
	bool operator < ( const Line &t ) const {
		return x != t.x ? x < t.x : y < t.y ;
	}
} ;

struct Node {
	int idx ;
	int c ;
	Node () {}
	Node ( int _c , int _idx ) : c ( _c ) , idx ( _idx ) {}
	bool operator < ( const Node &a ) const {
		return c < a.c ;
	}
} ;

struct Query {
	int ch , x , y ;
} ;

struct priority_queue {
	Node heap[MAXH] ;
	int point ;
	
	void init () {
		point = 1 ;
	}
	
	void push ( int c , int idx ) {
		heap[point] = Node ( c , idx ) ;
		int o = point ++ ;
		while ( o > 1 && heap[o] < heap[o >> 1] ) {
			swap ( heap[o] , heap[o >> 1] ) ;
			o >>= 1 ;
		}
	}
	
	void pop () {
		heap[1] = heap[-- point] ;
		int o = 1 , p = o , l = o << 1 , r = o << 1 | 1 ;
		while ( o < point ) {
			if ( l < point && heap[l] < heap[p] )
				p = l ;
			if ( r < point && heap[r] < heap[p] )
				p = r ;
			if ( p == o )
				break ;
			swap ( heap[o] , heap[p] ) ;
			o = p , l = o << 1 , r = o << 1 | 1 ;
		}
	}
	
	Node top () {
		return heap[1] ;
	}
	
	int front () {
		return heap[1].idx ;
	}
	
	int empty () {
		return point == 1 ;
	}
} ;

priority_queue q ;
Line a[MAXE] , b[MAXE] ;
Query Q[MAXQ] ;
int tot ;
int n , m , qu ;
int p[MAXN] ;
int val[MAXN] , ask[MAXM] ;
bool vis[MAXM] ;
int idx[MAXN] ;
char s[200] ;

int find ( int x ) {
	int ans , tmp , o = x ;
	while ( p[o] != o )
		o = p[o] ;
	ans = o ;
	o = x ;
	while ( p[o] != o ) {
		tmp = p[o] ;
		p[o] = ans ;
		o = tmp ;
	}
	return ans ;
}

void input () {
	REPF ( i , 1 , n )
		scanf ( "%d" , &val[i] ) ;
	REP ( i , m )
		a[i].input () ;
	scanf ( "%d" , &qu ) ;
	REP ( i , qu ) {
		scanf ( "%s" , s ) ;
		if ( s[0] == 'Q' ) {
			Q[i].ch = 0 ;
			scanf ( "%d" , &Q[i].x ) ;
		}
		else if ( s[0] == 'D' ) {
			Q[i].ch = 1 ;
			scanf ( "%d%d" , &Q[i].x , &Q[i].y ) ;
			if ( Q[i].x > Q[i].y )
				swap ( Q[i].x , Q[i].y ) ;
			b[tot].x = Q[i].x ;
			b[tot].y = Q[i].y ;
			++ tot ;
		}
		else
			Q[i].ch = 2 ;
	}
}

void build () {
	sort ( a , a + m ) ;
	sort ( b , b + tot ) ;
	int j = 0 ;
	REP ( i , m ) {
		if ( a[i].x == b[j].x && a[i].y == b[j].y ) {
			a[i].f = 0 ;
			++ j ;
			if ( j == tot )
				break ;
		}
	}
	REPF ( i , 1 , n )
		p[i] = i ;
	REP ( i , m )
		if ( a[i].f ) {
			int x = find ( a[i].x ) ;
			int y = find ( a[i].y ) ;
			if ( x != y ) {
				p[x] = y ;
				val[y] += val[x] ;
			}
		}
	REPF ( i , 1 , n )
		if ( p[i] == i ) {
			vis[i] = 1 ;
			idx[i] = i ;
			q.push ( val[i] , i ) ;
		}
}

void solve () {
	int cnt = n ;
	int num = 0 ;
	while ( qu -- ) {
		if ( Q[qu].ch == 0 ) {
			int x = find ( Q[qu].x ) ;
			ask[num ++] = val[x] ;
		}
		else if ( Q[qu].ch == 1 ) {
			int x = find ( Q[qu].x ) ;
			int y = find ( Q[qu].y ) ;
			if ( x != y ) {
				val[y] += val[x] ;
				p[x] = y ;
				vis[idx[x]] = 0 ;
				vis[idx[y]] = 0 ;
				idx[y] = ++ cnt ;
				vis[idx[y]] = 1 ;
				q.push ( val[y] , cnt ) ;
			}
		}
		else {
			while ( 1 ) {
				Node tmp = q.top () ;
				if ( vis[tmp.idx] ) {
					ask[num ++] = tmp.c ;
					break ;
				}
				q.pop () ;
			}
		}
	}
	REPV ( i , num - 1 , 0 )
		printf ( "%d\n" , ask[i] ) ;
}

void work () {
	while ( ~scanf ( "%d%d" , &n , &m ) ) {
		clear ( vis , 0 ) ;
		q.init () ;
		tot = 0 ;
		input () ;
		build () ;
		solve () ;
	}
}
		
int main () {
	work () ;
	return 0 ;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值