POJ2513 Trie+并查集+欧拉回路

题目大意:
给定一系列sticks,每个木棒的两端都涂有颜色,判断是否能够找到将所有的木棒连接起来的方法,使相互连接的木棒的两端的颜色是相同的?

分析:
画图分析可知,如果形成的图能有一条路径遍历所有的边并且不重复。则达到目的。不由想起欧拉回路(从某个节点出发,不重复的遍历所有路径,回到原点,则为此图的欧拉回路)。而本题中并不要求回到原点。没有欧拉回路要求的苛刻。判断无向图是否有欧拉回路的方法是图中所有节点的度数为偶数。那么对于此题来说,把条件放宽到图中要么没有奇数度数的节点,要么有两个。原因大家自己画画就明白了。总结结论如下:
1.该图必须是一个连通图
2.该图每个点的度数要么全为偶数,要么有且仅有两个点的度数为奇数

解决方法:
1.找出奇数点度数的结点个数,可以用Map计算出每个节点的度数。效率太低。可以采用字典树,查找效率快,同时可以记录某个串出现的度数。当然,我们的目的是计算奇数度数的结点个数,也就没有必要把所有的串插入完之后再去遍历一遍所有串的度数。巧妙地方法是,当我们插入某个串第奇数次时,加1,遍历第偶数此时-1.那么对于出现偶数次的串结果为0.最终我们只记录了出现奇数次的串的个数。
2.为了保证图的连通性,如果用传统方法遍历,效率必然很低。最好的办法就是并查集了(http://www.cnblogs.com/cherish_yimi/archive/2009/10/11/1580839.html)抄录如下:
并查集:(union-find sets)

一种简单的用途广泛的集合. 并查集是若干个不相交集合,能够实现较快的合并和判断元素所在集合的操作,应用很多,如其求无向图的连通分量个数等。最完美的应用当属:实现Kruskar算法求最小生成树。

I.并查集的精髓(即它的三种操作,结合实现代码模板进行理解):
1、Make_Set(x) 把每一个元素初始化为一个集合
初始化后每一个元素的父亲节点是它本身,每一个元素的祖先节点也是它本身(也可以根据情况而变)。
2、Find_Set(x) 查找一个元素所在的集合
查找一个元素所在的集合,其精髓是找到这个元素所在集合的祖先!这个才是并查集判断和合并的最终依据。
判断两个元素是否属于同一集合,只要看他们所在集合的祖先是否相同即可。
合并两个集合,也是使一个集合的祖先成为另一个集合的祖先,具体见示意图
3、Union(x,y) 合并x,y所在的两个集合
合并两个不相交集合操作很简单:
利用Find_Set找到其中两个集合的祖先,将一个集合的祖先指向另一个集合的祖先。如图
abc

 

II.并查集的优化

1、Find_Set(x)时 路径压缩
寻找祖先时我们一般采用递归查找,但是当元素很多亦或是整棵树变为一条链时,每次Find_Set(x)都是O(n)的复杂度,有没有办法减小这个复杂度呢?
答案是肯定的,这就是路径压缩,即当我们经过"递推"找到祖先节点后,"回溯"的时候顺便将它的子孙节点都直接指向祖先,这样以后再次Find_Set(x)时复杂度就变成O(1)了,如下图所示;可见,路径压缩方便了以后的查找。
2、Union(x,y)时 按秩合并
即合并的时候将元素少的集合合并到元素多的集合中,这样合并之后树的高度会相对较小。

 

 

附上代码:

#include<iostream>
using namespace std;

int num = 0;
int num_of_odd = 0;
//Trie
struct Node{
	int id;//表示一个串,为了给并查集提供服务
	int cnt;//系统存储了多少个本串
	Node* next[26];
	Node(){
		id = -1;
		cnt = 0;
		int i = 0;
		for(i = 0; i < 26; i++){
			next[i] = NULL;
		}
	}
};

Node* root = new Node();

int insert(char* str){
	Node* p = root;
	int i = 0;
	for(i = 0; i < strlen(str); i++){
		if(p->next[str[i] - 'a'] == NULL){
			Node *q = new Node();
			p->next[str[i] - 'a'] = q;
		}
		p = p->next[str[i] - 'a'];
	}

	p->cnt++;
	if(p->cnt & 1){
		num_of_odd++;
	} else {
		num_of_odd--;
	}

	if(-1 == p->id){
		p->id = ++num;
	}
	return p->id;
}

//并查集	
int rank[500002] = {0};
int parent[500002] = {0};
int find_set(int x) {
    if (0 == parent[x]) {
       return -1;
    }

    if (parent[x] != x) {
        parent[x] = find_set(parent[x]);
    }

    return parent[x];
}

void make_set(int x) {
    if (find_set(x) == -1) {
        parent[x] = x;
        rank[x] == 0;
    }
}

void union_set(int x, int y) {
    x = find_set(x);
    y = find_set(y);
    if (x == y) return;

    if (rank[x] == rank[y]) {
        parent[x] = y;
        rank[y]++;
    } else if (rank[x] < rank[y]) {
        parent[x] = y;
    } else {
        parent[y] = x;
    }
}



int main(){

//	freopen("2513.txt", "r", stdin);

	char str1[11];
	char str2[11];
	while(scanf("%s", str1) != EOF){
		scanf("%s", str2);
		int a = insert(str1);
		int b = insert(str2);
		make_set(a);
		make_set(b);
		union_set(a, b);
	}
	
	if(num_of_odd != 0 && num_of_odd != 2){
		cout << "Impossible" << endl;
		return 0;
	}
	int i = 0;
	int temp = find_set(1);
	for(i = 2; i <= num; i++){
		if(find_set(i) != temp){
			break;
		}
	}
	if(i <= num){
		cout << "Impossible" << endl;
		return 0;
	}
	cout << "Possible" << endl;
	return 0;
}


 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值