PQ树小结

目标

求一个排列,满足若干限制,限制内容是:某个集合中的数,在最后生成的排列中要放在一起。

节点

首先,PQ树是一棵树。
叶节点和两种非叶节点,定义一个点的类型:
叶节点:代表0~n-1的一个数
非叶节点P:表示其儿子节点可以任意交换顺序 排列
非叶节点Q:表示其儿子节点只可 正序反序 排列(注意,只有儿子结点,不是子树)

在每次新增过程中,首先会给叶子节点标注关键点(即需要并在一起的数),然后对于每个树中的节点定义一种颜色:
:这个节点代表的子树中没有关键点
:这个节点代表的子树中部分是关键点
:这个节点代表的子树中全是关键点

注意到,我们为了编写程序的方便,利用son,brother的方式构造一棵树(类似左儿子右兄弟的意思),这样构建方便于连接两棵树、剖开一棵树、以及新建一棵树

注意到,如果一个PQ节点,没有儿子、有一个儿子,这两种情况下,这个PQ节点是无用的,并会影响复杂度,所以需要去掉,即PQ节点至少有两个儿子

操作

先定义一些小操作:

node *newSubtree(node *p,int pv):把p以及p的兄弟全部挂在一个新建的、类型为pv的节点(如果p没有兄弟,直接返回p)
node *reverseBro(node *p):reverse的辅助函数,把p以及右边兄弟全部翻转表示
void reverse(node *p):把p的儿子们翻转表示(即改变son为最后一个儿子,并翻转所有儿子),注意,这里之翻转p的儿子,而不是p这棵子树
void alignRight(node *p):把p的儿子按照三色排序(可能无法满足)
对于P节点来说,直接排序即可
对于Q节点来说,只有正序反序两种情况,正序不行只好输出反序,故可能无法满足
bool checkRight(node *p):判断它是正确排放,并且灰点数<=1

node *splitWhite(node *p):把p与其儿子中白色节点分为一类,灰黑色节点分为一类,两者分离,并把后者头指针回传
int cntNGray(node *p):数p、以及p向后的兄弟节点中,有多少个灰色节点(注意是从p开始所有的兄弟)

然后到主结构体的操作:

void init(int n):初始化,即把树定义成一个P节点挂0~n-1这些叶节点
void permutationDfs(node *p,int *&out):permutation的辅助函数
void permutation(int *out):跑一遍树,把排列输出到out中
char* insertS:辅助insertPreDfs的数组,即输入的限制,用字符串表示,a[i]==’1’代表限制中有i这个数
void insertPreDfs(node *p):把整棵树染色,按照定义中的方法染成黑白灰三色
bool insertDfs(node *p):最重要的函数,判断是否成功插入,并修改整棵树,重点讨论所有情况,下文细说
bool insert(char *str):插入一个限制,并判断是否成功

insertDfs

0. 如果p是黑点白点、或者叶节点,直接退出(由于编写效率问题,中间新增的节点节省计算颜色的步骤,所以可能导致叶节点不是黑白色,故加入叶节点的特判)

1. 如果p是P类节点:

首先不妨把叶节点按颜色排序alignRight,这步一定是正确

1.0 如果p有白儿子,那么新建一个P节点np,把剩下灰/黑儿子挂在np下,np成为p的新儿子,并递归操作np,这里把np的颜色设为(注意到此时这点可能实际是或者是,所以这里可能的设置与实际不符,故需要在递归中解决这个实际的问题)

1.1 如果p没有白儿子,由于任何时候灰儿子只可能<=2个,否则一定无解(画图可知)

1.1.0 如果没有灰儿子,那么只剩下黑儿子,那么这个点就是个实际的黑点,直接返回即可

1.1.1 如果只有一个灰儿子,那么注意到,此时必有黑儿子(否则,就只有一个儿子了),那么新建np,把黑儿子挂在np下,把np颜色设为,注意到此时黑点集*np必须与灰色点h邻接,故首先,我们需要把p从P类改成Q类,然后还需要灰色点*满足与np邻接的条件,这个条件与“如果p是Q类节点”操作是一致的,故可以理解为p改为Q类以后再次操作,操作在下文中叙述

1.1.2 如果有两个灰色儿子pl,pr,类似1.1.1,把剩下的黑儿子做成**黑色点集**np,连接pl->np->pr,同样地,需要三者邻接;同样地,与“如果p是Q类节点”;同样地,在下文叙述

2. 如果p是Q类节点:

首先找第一个非白儿子 fs 和最后一个非白儿子 ls(开始设为空)

2.0(a) 如果fs==ls,那么只有唯一一个非白儿子,或没有(如果都为空),递归即可(这里不需要把白儿子新建点,因为p是Q类。。相对位置是固定的两种)

2.0(b) 如果fs、ls之间有非黑节点,那么,无解(无法绑定在一起了,因为Q的原因)

接下来我们要做的事情,无非是把一个灰色PQ节点剖开,并按正确的排列方式让他们挂在p下,不妨操作fs(对于ls,把节点p翻转后,交换fs,ls,之后对fs同样操作即可,(靠左靠右的问题得以解决))

2.1.0 首先正确的排列方式即:fs的儿子按照靠右摆放alignRight后,并且是正确的,且至多一个灰色儿子,即checkRight(fs)(否则画图可知无解)

2.1.1.1 如果fs是个P类点,分离白点集给fs,新建节点h,把黑色儿子挂在h下面,并设h的颜色为,然后依次在原fs同级上依次接入fs的白点集**fs、灰点(可能没有)、黑点集**h节点。

2.1.1.2 如果fs是个Q类点,那么直接“爆开”fs即可,把fs的白儿子都接到fs同级上,并把fs重定向到最右的一个白点。(原fs变为无用节点)

2.1.2最后把fs指向新的原fs之下的“新灰点”(即fs->bro)

2.2最后清理所有上述的无用节点

综上。。。比较有趣的是,观察到了绑定一个集合中的数以后,实际上自由被限定可以用一个树形结构来表示,然后如果两个集合同时限定这个数的时候,实际上就只牵涉到限定了多棵子树的相对位置,并且注意只有正反两种情况,第二个观察是非常重要的。。

猜你喜欢

转载自blog.csdn.net/yeziqing10/article/details/51997188
pq