题目大意:
给定一系列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找到其中两个集合的祖先,将一个集合的祖先指向另一个集合的祖先。如图
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; }