题目大意:
给定一系列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;
}