// 1st AC 30052K 4313MS G++
#include <cstdio>
// #include <malloc.h>
#include <cstring>
#include <cstdlib>
#include <iostream>
using namespace std;
const int CHAR_NUM = 26;
struct tireNode {
// char val[20];
int tireChildNode[CHAR_NUM];
int appearTime;
int colorId; // begin from 1
};
typedef struct tireNode tireNode;
tireNode staticTireTreeArray[250001];
int staticTireTreeArraybeginPos = 26; // 0~25 has been placed
int colorMap[250001];
int colorTypeNum = 0;
int union_find_array[250001];
int set_num = 0;
int buildTireTree(char * word) {
int wordLength = strlen(word);
int currentNodePos = word[0] - 'a';
for (int i = 1; i < wordLength; i++) {
if (!staticTireTreeArray[currentNodePos].tireChildNode[word[i]-'a']) {
staticTireTreeArray[currentNodePos].tireChildNode[word[i]-'a'] = staticTireTreeArraybeginPos++;
}
currentNodePos = staticTireTreeArray[currentNodePos].tireChildNode[word[i]-'a'];
}
if (!(staticTireTreeArray[currentNodePos].appearTime)) { // this color never appear before
staticTireTreeArray[currentNodePos].colorId = ++colorTypeNum; // colorId begin from 1
}
colorMap[staticTireTreeArray[currentNodePos].colorId]++;
(staticTireTreeArray[currentNodePos].appearTime)++;
// printf("build %d %d\n", staticTireTreeArray[currentNodePos].colorId, staticTireTreeArray[currentNodePos].appearTime);
return staticTireTreeArray[currentNodePos].colorId;
}
void union_set(int root1, int root2) { // union root1 as root2's subtree
set_num--;
union_find_array[root1] = root2;
}
int getRoot(int color) {
while(color != union_find_array[color]) {
color = union_find_array[color];
}
return color;
}
void union_find(int leftColor, int rightColor) {
// printf("union_find %d %d %d %d\n", leftColor, rightColor,
// union_find_array[leftColor], union_find_array[rightColor]);
if (union_find_array[leftColor] == 0 &&
union_find_array[rightColor] == 0) { // both belong to a new set
union_find_array[leftColor] = leftColor; // root color as set's id
union_find_array[rightColor] = leftColor;
set_num++;
} else if (union_find_array[leftColor] == 0 &&
union_find_array[rightColor] != 0) { // only rightColor belong to a set, add leftcolor
union_find_array[leftColor] = getRoot(rightColor); // set rightcolor's root as its parent
} else if (union_find_array[leftColor] != 0 &&
union_find_array[rightColor] == 0) { // only left color belong to a set, add rightcolor
union_find_array[rightColor] = getRoot(leftColor); // set leftcolor's root as its parent
} else { // both belong to a different set, union them.
int leftRoot = getRoot(leftColor);
int rightRoot = getRoot(rightColor);
if (leftRoot != rightRoot) {
union_find_array[leftColor] = leftRoot;
union_find_array[rightColor] = rightRoot;
union_set(leftRoot, rightRoot); // left as right subtree;
}
}
}
char existEulerPath() {
int odd_num = 0;
for (int i = 1; i <= colorTypeNum; i++) {
if (colorMap[i]%2) { // odd_degree
if ((++odd_num) > 2) {
return false;
}
}
}
return odd_num == 1 ? false : true;
}
int main() {
char c1[15] = {0};
char c2[15] = {0};
memset(staticTireTreeArray, 0, sizeof(staticTireTreeArray));
staticTireTreeArraybeginPos = 26;
memset(colorMap, 0, sizeof(colorMap));
colorTypeNum = 0;
memset(union_find_array, 0, sizeof(union_find_array));
set_num = 0;
int stickNum = 0;
while(cin>>c1>>c2) {
// if (!strcmp(c1, "end") && !strcmp(c2, "end")) {
// break;
// }
if (cin.eof()) {
break;
}
if (strlen(c1) == 0 || strlen(c2) == 0) {
break;
}
stickNum++;
int leftColor = buildTireTree(c1);
int rightColor = buildTireTree(c2);
union_find(leftColor, rightColor);
}
if (!stickNum) { // no data, possible
printf("Possible\n");
return 0;
}
if (set_num == 1) {
existEulerPath() ? printf("Possible\n") : printf("Impossible\n");
} else {
printf("Impossible\n");
}
}
前几次因为数组不够大和没有处理空输入导致了RE.
discuss里一个400多ms的结果是WA....
某个在C++下2000+ms的AC在G++下直接TLE....
采用动态tire树直接TLE...
第一次AC,G++(C++ RE...) 30052K 4313MS.... 已经使用了静态的tire树, 在判断图连通时用的是并查集,并查集采用的是静态树形式来组织,但是合并时没有按照树的高度进行合并,可能会有效率损耗。
后来将数组的大小从250000减到25000 (2500 RE,空间不够),3604K 4204MS,符合预期,空间优化,时间仍未有优化.
,将并查集的set之间合并按照大树吞并小树的优化思路进行,4188MS 没有提升.
先进行欧拉路判断,然后再并查集检测连通性,理论上在某些情况可以提速,但是本题用这种方法无提升.
尝试了一些优化手段,结合其他的code来看,似乎用tire+并查集+欧拉回路似乎基本是这个效率。
学习完了并查集和欧拉回路就开始搞这道题了。
并查集之前听名字觉得是个很高大上和复杂的算法,不过看完以后,发现并查集的中等用法其实还是比较简单的,
并查集, 叫“不相交集合”,总之,并查集的主要用途就是能将一坨坨的数据根据某种规则进行无视顺序的分类(非为一个个集合set),每一个set的元素都具有某种相同特性(在本题,就是互为连通)。
并查集的数据结构一般都是直接用线性数组(集合对应数组),每个元素对应数组中的一个位置,该位置保存的值则是此元素应该归属的set的id(一般也是用数字表示,1,2,3..., set id一般可以用set中的某个元素来直接代表) 这样在每次有进来新元素群(一般应该不是一个元素,而是一群元素,这群元素是属于一个集合的, 如果一次进入一个元素,那么每个元素之间就没有了联系和共性)的时候,通过查看这群元素在集合对应数组的set id即可,这时候,会有几种case:
case 1:这群新元素的set id都是初始状态,即之前还没有关于这群元素的任何联系关系被处理过,那么简单了,这群元素就一起组成一个新的set,set id取当前可用的最小的
set id即可
case 2:这群新元素中只有一个的set id被设置过为k,意味着,这个元素一定和set k 的某个元素有联系,属于set k,那么因为其他的新元素就和该元素属于同一set,因此其他set id没有被设置的元素的set id都应该是k, 即set k中加入了一群的新元素。
case 3:这群新元素中有N个(N>1)个新元素的set id已经被设置过了,再次细分case:
sub-case1: 这N个新元素都属于同一个set,那么同case2处理
sub-case2: 这N个新元素分属于 > 1个set,那么就表明这次新输入的元素关系将多个原来没有联系的set都连接在了一起,组成一个大的set, 这些被合并的set中的元素要修改自己的set id来表示此次合并. 合并的时候,要更新set的数量,减去因为合并而减少的数量。
最后,当想要查看输入的这一堆的元素之间是否连通的,只需查看最后set的数目即可,如果只有一个set,那么必然所有的元素都属于一个set,都是连通的。
如果有>1个set,那么说明这群元素不是完全连通的, 某一群和另一群之间没有任何联系,不连通.
并查集在检测图是否连通很有效,但是也仅限于是否连通,如果要进一步求出连通路径,就没办法了。
并查集的一些优化:
首先在case3的sub-case2中,在合并set的时候,要修改一堆的元素的set id,算下来时间开销是很大的,那么采用树形结构就可以避免这个合并要修改很多元素的问题:
在集合对应数组中,某个元素的值不再是自己所在的set id,而是自己的父亲元素(即通过和他建立联系将他拉到这个set的元素),一个set有一个root,
这个root的父亲元素就是自己,而这个set也可用这个root来进行标示,当要找一个元素的set id的时候,只需一路沿着父节点向上最后找到root即可,在有新的连接元素进来时,只需设置其数组值为其父亲节点的值即可,比如, 某个set 有这样的关系 2->3->4->5, 2是root, 这时候来了一个新的联系:(5,8)表示5与8是有联系的,属于一个set,
那么8的set id设置为5即可(因为是5把他拉进这个set里), 这样做的好处就是,当合并set时,不需要修改set的每个成员的set id,只需要将被合并的set的root的set id从自己修改为吞并他的set的root即可(很巧妙,也充分体现了指针的灵活,而这一切却本身又是在线性数组上模拟出来的,因为这里的指针其实只是元素的某个位置值,我发现这种预先分配线性数组 + 模拟指针是很好的方案,既有了指针的灵活,又有了线性预分配数组的效率) 。而且为了合并的set树的高度最小,一般是最大的set吞并其他的set。
上面这个方案,还有一个优化就是,现在每个元素其对应数组位置存储的是父节点索引,要想得到某个元素的set root id,必须从下到上遍历,这也会带来性能的损耗,可以这样做,在对某个元素求出来其set root id以后,直接将该数组的set id修改为root id,这样下次再需要时,就可以直接用,并且合并set也不会影响该方法的正确性,最多只是在求root id时又要向上遍历(因为被合并的set数的root已经不是root了,因此在求root id的过程中,到了他这里,已经不再满足 他的父节点就是自己的条件了,要继续向上).
下面就是对题目的分析了,这道题的本质就是求一个欧拉路(不是回路,因为只要求stick排成一排,没要求首位还要相连),只不过为了把字符串颜色值转化为数字,要用tire树进行一次预处理,得到颜色字符串和数字的对应关系,然后再在这个基础上求连通图的欧拉路。
题的意思是说,在最后的组成一列时,每个stick和它相邻的两个stick,连接点的颜色都必须一样, 比如 blue<-->red red<-->green green<-->blue blue<-->red
如果把一种颜色看做是图的一个节点的话(这个节点会跟其他若干个颜色点有连通,取决此拥有此颜色的stick的另外一端一共有多少种颜色),这个问题就会转为为
求这个图的欧拉路是否存在,即能够每条边都经过一次,这里的每条边就对应这一个stick, 比如 red 到 blue之前有两条无向连接,这就代表有两个stick 这两个stick两端的颜色都是 red和blue(这个转化其实也不好想,偏经验性的),这样如果欧拉路存在,那么就会有一条只经过每条边一次的路径,这条路径就是题目要求的stick排成一列。
求图欧拉路的前提是图必须是连通的,即属于的颜色之间都是应该有联系的,属于一个set,从某个颜色通过stick的颜色pair联系 必能到达另一个颜色,
比如, 4个stick, red<-->blue blue<-->green green<-->black, white<-->gray, 从red到black是可达的, 到white则是不可达的。 求这个图是否连通,就用到了上面的并查集,每个颜色代表一种元素,而一类stick则代表某两个元素直接的联系,最后看是否所有的元素都属于一个set,如果都属于,那么就是连通的,否则,不是连通图,那么也不可能有欧拉路。
而欧拉路,因为本图是无向图,只需按照这个规则求即可:
无向图:图连通,只有两个顶点是奇数度,其余都是偶数度的。
这里每个点(颜色)的度就是这种颜色的stick一共有多少个(有多少根,就有多少个到其他节点(颜色)的连接边,题目也保证了不会有一个stick两端相同颜色),之前在tire数预处理颜色字符串的时候预先记录每种颜色的出现次数即可。
这道题做起来真是十足过瘾,3个类别的问题合并,问题的转化也很有意思,是个图应用的经典范例。并查集也是第一应用,值得牢记。欧拉路在以后可能会遇到更多的转化模型.