POJ #2513 Colored Sticks 连通图 欧拉通路 并查集 Trie树 动态/静态建树
Description
You are given a bunch of wooden sticks. Each endpoint of each stick is colored with some color. Is it possible to align the sticks in a straight line such that the colors of the endpoints that touch are of the same color?
Input
Input is a sequence of lines, each line contains two words, separated by spaces, giving the colors of the endpoints of one stick. A word is a sequence of lowercase letters no longer than 10 characters. There is no more than 250000 sticks.Output
If the sticks can be aligned in the desired way, output a single line saying Possible, otherwise output Impossible.Sample Input
blue red red violet cyan blue blue magenta magenta cyanSample Output
PossibleHint
Huge input,scanf is recommended.
思路
这道题比较难,不论是从建图的逻辑还是从优化上看。
让同一个颜色的端点互相接触且将木棒排成一条直线呢?把木棒看成边,木棒两端的颜色看成结点,如果颜色相同则归为同一个结点,建立图。排为直线的意思即是每条边只允许经过一次且图是连接图(包含了所有顶点)。
每条边只允许经过一次让我想到了欧拉XX,这里先复习一下关于图论中欧拉XX的知识:
1.欧拉回路:图 G 中的一个路径包括每条边恰好一次,则称为欧拉路径。如果一个回路是欧拉路径,则称为欧拉回路。
判断欧拉回路的充要条件:图中结点度数全为偶数时,该图有欧拉回路。
2.欧拉通路:“一笔画”问题,即可以不回到起点,但必须经过每一条边,且只能一次。
判断欧拉通路的充要条件:当图中只存在 0 个或 2 个度数为奇数的结点时,该图有欧拉通路。
3.欧拉图:具有欧拉回路的图称为欧拉图
从上面判断欧拉通路的充要条件可以解决每条边仅允许过一次的问题,而且很容易。那么只剩下一个问题了:判断图是否为连通图。
这个可以转化思路为图中仅存在一个连通分量,而求连通分量最快的办法就是并查集。但是还有个问题,并查集处理的是顶点编号,而输入的是大量字符串,如何得到顶点编号呢?
一看到大量字符串的“存储+查询”问题,就立刻想起了STL中 hash_table 实现的 map,无奈发现这题卡 map,所以采用了 Trie 树,也称字典树、前缀树,在算法导论里还称它为基数树,它可以利用串的公共前缀节约查询时间,解决了存储后的暴力查询问题。静态 Trie 树查询的时间复杂度为 O(len),有时甚至快过 hash_table O(1) (然而实际 hash_table 也是 O(len)),静态 Trie 树当然也有缺点:需要空间非常大,需要开大数组。但是如果用动态 Trie 树,会发现 new 结点的时间实在太慢了,还不如自己写一个字符串 hash 。
AC代码如下,第一个版本是动态建 Trie 树,第二个版本是静态建 Trie 树:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream> #include<algorithm> #include<cstring> #include<cstdio> using namespace std; #define MAXN 250005 //建图,以顶点为颜色、边为木棒建立无向图 int degree[MAXN*2]; //每个结点的度 int v_num; //顶点个数 //POJ没开优化,STL map 会TLE //Trie树存储顶点编号 struct Node { Node *next[26]; //一个结点的next结点,包含26种可能 int id; bool flag; Node() { for (int i = 0; i < 26; i++) next[i] = NULL; flag = false; id = -1; } }; Node *root; void initTrie(){ root = new Node(); } //将字符串记录于Trie树中,并返回通过该函数得到顶点编号 int build_fid(char *s) { int len = strlen(s); Node *now = root; for (int i = 0; i < len; i++) { int to = s[i] - 'a'; //int to = trans(s[i]); if (now->next[to] == NULL) now->next[to] = new Node(); now = now->next[to]; } if (now->flag) return now->id; //返回顶点的编号 else { now->flag = true; now->id = ++v_num; //添加顶点 return now->id; } } //并查集求连通图 int p[MAXN*2]; int findSet(int i) { int tmp; //查找 i 的根 for (tmp = i; p[tmp] >= 0; tmp = p[tmp]); //压缩路径 while (tmp != i) { int t = p[i]; p[i] = tmp; i = t; } return tmp; } void unionSet (int a, int b) { int r1 = findSet(a), r2 = findSet(b); int tmp = p[r1] + p[r2]; //-tmp表示两集合结点数之和 if (p[r1] < p[r2]) { p[r2] = r1; p[r1] = tmp; } else { p[r1] = r2; p[r2] = tmp; } } //判断图是否连通与是否为欧拉通路 bool check () { int odd_num = 0; //记录奇数度顶点的个数 int cnt = 0; //记录连通分量的个数,>1 说明图不连通 for (int i = 1; i <= v_num; i++) { if (p[i] < 0) ++cnt; // <0 说明其是该集合的代表 if (cnt > 1) return false; if (degree[i]%2) odd_num++; if (odd_num > 2) return false; } if (odd_num == 0 || odd_num == 2) return true; } int main(void) { memset(degree, 0, sizeof(degree)); v_num = 0; memset(p, -1, sizeof(p)); initTrie(); char s1[11], s2[11]; //多一个单元存储'\0' while (scanf("%s%s", s1, s2) != EOF) { int x = build_fid(s1), y = build_fid(s2); //无向图中,一个结点的度就是有多少边与其相连 //对于本题:加一条边(木棒),两种颜色的顶点的度++ degree[x]++; degree[y]++; if (findSet(x) != findSet(y)) unionSet(x, y); } printf("%s\n", check() ? "Possible" : "Impossible"); return 0; }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream> #include<algorithm> #include<cstring> #include<cstdio> using namespace std; #define MAXN 250005 //最大木棒数 //建图,以顶点为颜色、边为木棒建立无向图 int degree[MAXN*2]; //每个结点的度 int v_num; //顶点个数 //POJ没开优化,STL map 会TLE //Trie树存储顶点编号 struct Node { Node *next[26]; //一个结点的next结点,包含26种可能 int id; bool flag; }trie[700000]; //需要开大数组,因为数组包括了众多next结点 Node *root; int move; //静态建树特点,记录用了几个Node,则下一个结点是trie[move] void initTrie(){ move = 0; root = &trie[move++]; for (int i = 0; i < 26; ++i) root->next[i] = NULL; } //建立与查询Trie,并通过该函数得到顶点编号 int build_fid(char s[11]) { int len = strlen(s); Node *now = root; for (int i = 0; i < len; i++) { int to = s[i] - 'a'; //int to = trans(s[i]); if (!now->next[to]) { //“申请”新结点 now->next[to] = &trie[move++]; now->next[to]->flag = false; for (int i = 0; i < 26; i++) now->next[to]->next[i] = NULL; } now = now->next[to]; } if (now->flag) return now->id; //返回顶点的编号 else { now->flag = true; now->id = ++v_num; //添加顶点 return now->id; } } //并查集求连通图 int p[MAXN*2]; int findSet(int i) { int tmp; //查找 i 的根 for (tmp = i; p[tmp] >= 0; tmp = p[tmp]); //压缩路径 while (tmp != i) { int t = p[i]; p[i] = tmp; i = t; } return tmp; } void unionSet (int a, int b) { int r1 = findSet(a), r2 = findSet(b); int tmp = p[r1] + p[r2]; //-tmp表示两集合结点数之和 if (p[r1] < p[r2]) { p[r2] = r1; p[r1] = tmp; } else { p[r1] = r2; p[r2] = tmp; } } //判断图是否连通与是否为欧拉通路 bool check () { int odd_num = 0; //记录奇数度顶点的个数 int cnt = 0; //记录连通分量的个数,>1 说明图不连通 for (int i = 1; i <= v_num; i++) { if (p[i] < 0) ++cnt; // <0 说明其是该集合的代表 if (cnt > 1) return false; if (degree[i]%2) odd_num++; if (odd_num > 2) return false; } if (odd_num == 0 || odd_num == 2) return true; } int main(void) { memset(degree, 0, sizeof(degree)); v_num = 0; memset(p, -1, sizeof(p)); initTrie(); char s1[11], s2[11]; //多一个单元存储'\0' while (scanf("%s%s", s1, s2) != EOF) { int x = build_fid(s1), y = build_fid(s2); //无向图中,一个结点的度就是有多少边与其相连 //对于本题:加一条边(木棒),两种颜色的顶点的度++ degree[x]++; degree[y]++; if (findSet(x) != findSet(y)) unionSet(x, y); } printf("%s\n", check() ? "Possible" : "Impossible"); return 0; }
延伸阅读