poj 2513 Colored Sticks【trie+并查集+欧拉回路】
大致题意:
给定一些木棒,木棒两端都涂上颜色,求是否能将木棒首尾相接,连成一条直线,要求不同木棒相接的一边必须是相同颜色的。
解题思路:
可以用图论中欧拉路的知识来解这道题,首先可以把木棒两端看成节点,把木棒看成边,这样相同的颜色就是同一个节点
问题便转化为:
给定一个图,是否存在“一笔画”经过涂中每一点,以及经过每一边一次。
这样就是求图中是否存在欧拉路Euler-Path。
由图论知识可以知道,无向图存在欧拉路的充要条件为:
① 图是连通的;
② 所有节点的度为偶数,或者有且只有两个度为奇数的节点。
View Code
#include<stdio.h> #include<string.h> #include<iostream> #include<string> using namespace std; const int MAXN = 500000 + 10; int color = 0; //颜色的总个数。也是分配给每个字符串的编号,从0开始分配。 class TrieTree_Node //字典树节点 { public: bool flag; //标记从根节点到当前字典树节点是否为一个单词。 int id; //如果为一个单词,则给它一个id。 TrieTree_Node *next[27]; TrieTree_Node() //initial { flag = false; id = 0; memset(next, 0, sizeof(next)); } }root; //字典树根节点。 //利用字典树构造字符串s到编号int的映射。 int hash(string s) { TrieTree_Node *p = &root; //从TrieTree的根节点出发搜索单词(单词不存在则创建) int len = 0; while(s[len] != '\0') { int index = s[len++] - 'a'; //把小写字母a~z映射到数字的0~25,作为字典树的每一层的索引. if(!p->next[index]) { //如果索引不存在,则构建索引。 p->next[index] = new TrieTree_Node; } p = p->next[index]; } if(p->flag) { //颜色单词已存在 return p->id; //返回其编号 } else { //否则创建单词 p->flag = true; p->id = color++; return p->id; //返回分配给新颜色的编号。 } } //并查集判断图是否连通。 int parent[MAXN]; int Find(int x) { int s = x; //查找位置。 while(parent[s] >= 0) { s = parent[s]; } //压缩路径。 while(s != x) { int temp = parent[x]; parent[x] = s; x = temp; } return s; } void Union(int R1, int R2) { int r1 = Find(R1); int r2 = Find(R2); if(r1 == r2) return ; int tmp = parent[r1] + parent[r2]; if(parent[r1] > parent[r2]) { parent[r1] = r2; parent[r2] = tmp; } else { parent[r2] = r1; parent[r1] = tmp; } } int arr[MAXN]; //存放每个节点度的个数,用来判断欧拉回路。 int main() { // string a, b; char a[11], b[11]; memset(parent, -1, sizeof(parent)); memset(arr, 0, sizeof(arr)); while(scanf("%s %s", a, b) != EOF) { int r1 = hash(a); int r2 = hash(b); Union(r1, r2); arr[r1]++; arr[r2]++; } //这道题有读入空数据的情况,当读入的数据为空时,color为0,此时输出Impossible。 if(color == 0) { cout << "Possible\n"; return 0; } //判断图是否连通。 if(-parent[Find(0)] != color) { //如果不连通。 cout << "Impossible\n"; return 0; } //判断是否存在欧拉回路,如果不存在,结束。 //sum为基数节点的个数,基数节点个数等于1或大于等于3,则不存在欧拉回路。 int sum = 0; for(int i = 0; i < color; i++) { if(arr[i]%2 == 1) { sum++; } if(sum >= 3) { cout << "Impossible\n"; return 0; } } if(sum == 1) { cout << "Impossible\n"; return 0; } cout << "Possible\n"; return 0; }
黄老师的代码,比我效率高,有时间仔细看看,2点半了。
View Code
// poj 2513 Colored Sticks // trie树+欧拉回路+并查集 /* 1 我的trie写的是二叉树形式,有些麻烦,但是空间利用率高。 2 我写并查集的find函数时,把==写成了=,真是悲催啊 3 */ #include <stdio.h> #include <string.h> const int maxn = 260000; const int colorlen = 16; struct Node{ // trie树构建成一个二叉树,左儿子右兄弟,而非传统的父节点含26个子树的树,不过我觉得这也不像二叉树了,不信你把父节点连同他的右子树以及右子树的右子树放在一条直线上看一下 char c; int key; Node *l, *r; // l 是 c 匹配成功后的下一个匹配开始,r 是 c 匹配失败后下一个匹配的字符 }; Node trie[maxn*8]; int trie_pos; int euler[maxn*2+2]; /// euler path Node *root; // trie tree int set[maxn*2+2]; int key; // key , number void init() { trie_pos = 0; key = 1; root = NULL; memset(euler, 0, sizeof(int)*(maxn*2+2)); for (int i = 0; i < maxn*2+2; i++ ) set[i] = i; } int find(int x) { if (set[x] == x){ // Fuck,写成了 = 赋值的了 return x; } return set[x] = find(set[x]); } void union_set(int x, int y) { int fx = find(x); int fy = find(y); if (fx != fy){ set[fy] = fx; } } int search(char word[]) { int pos = 0; if (root == NULL){ root = &trie[trie_pos++];//new Node; root->key = 0; root->l = root->r = NULL; root->c = word[pos]; } Node * p = root, *father = root; Node ** pre = NULL; // pre 指针的指针相当重要,他记录上一次是从父亲的哪个子树开始的, while (word[pos] != '\0'){ if (p == NULL){ //printf("new Node:%c\n", word[pos]); p = &trie[trie_pos++];//new Node; p->key = 0; p->l = p->r = NULL; p->c = word[pos++]; (*pre) = p; // printf("pre new:%x\n", *pre); father = p; pre = &(p->l); //错在的这里啊,当一个新的节点创建好后,就应该从下一个节点,即左子树开始检测,则记录相应位置的pre也要更改 // pre 指针的指针,相当重要,他记录上一次是从父亲的哪个子树开始的 p = p->l; } else if (word[pos] == p->c){ // printf("Next l Node:%c\n", word[pos]); pre = &(p->l); // pre标明是从哪个子树开始的 father = p; p = p->l; ++pos; } else if (word[pos] != p->c){ // printf("Next r Node:%c %c\n", word[pos], p->c); pre = &(p->r); // pre标明是从哪个子树开始的 father = p; p = p->r; } else{ // return printf("Error\n"); while (1) printf("Error\n");; } } if (father->key == 0){ father->key = key++; } return father->key; } int main() { char str1[colorlen], str2[colorlen]; init(); while (2 == scanf("%s %s", str1, str2)){ int a = search(str1); // printf("%s %d\n", str1, a); int b = search(str2); // printf("%s %d\n", str2, b); euler[a]++; euler[b]++; union_set(a, b); } int euler_cnt = 0; int father_cnt = 1, father; father = find(1); for (int i = 1; i < key; i++){ euler_cnt += (euler[i] % 2); int t = find(i); //printf("$ %d %d\n", i, t); father_cnt += (t != father); } /* 我又杯具了,结果判断这里把father和father_cnt弄反了,*/ //printf("father %d, euler_cnt %d key %d\n", father, euler_cnt, key); if (father_cnt > 1 || euler_cnt > 2 || euler_cnt == 1){ // 这个输出答案的时候也出错了,逻辑啊 printf("Impossible\n"); } else{ // 空输入也是Possible printf("Possible\n"); } return 0; }