异或树

来源:互联网 发布:公司网络布局 编辑:程序博客网 时间:2024/05/16 07:05

下面两个问题:

1.给定一棵有 N (1 < N < 500000)个节点的树,定义两点之间的异或距离为这两个点之间的路径上的边的长度 L 

( 0 < L < 500000)的异或值,问有多少点对的异或距离等于 K (0 < K < 500000)。

2.给定一棵有 N (1 < N < 500000)个节点的树,定义两点之间的异或距离为这两个点之间的路径上的边的长度 L

(0 < L < 2 的31次方 -1)的异或值,问有多少点对的异或距离小于 K(0 < K < 2 的31次方 -1)。

两个点之间的路径是惟一的,这是因为树中不存在环,所以不存在多条可达路径。(树的结构是:每个节点有一个或者多个子节点,一个节点只有一个父亲节点,不同节点的子树不相交(不存在环结构))。所以上面两个问题中,点对的路径是惟一的,所以两个点的异或距离是惟一的。如图 1 ,举例说明两个点之间的异或距离。


图1 两点之间的异或距离

再来看两个问题,很接近,第一个问题是等于,第二个问题是小于。先解决第一个。

1.问题1(不再缀述)

首先考虑到任意两个点之间都是连通的,而且一个路径包括了另一个路径,如,c 到 h 的路径上包括了 c 到 g ,b 到 e 等路径,所以,此题可以用这样的办法来解:枚举所有叶子节点,深度遍历到另一个叶子节点结束遍历,直到所有叶子节点被遍历到停止枚举。假设有 n 个叶子节点,则以此法枚举路径,会得到 n * (n - 1) / 2 条路径(n 的平方),然后在各条路径中计算哪些子路径的异或距离等于 k (n 的平方),进行计数,于是问题得解。但这种方法的时间复杂度是:n 的四次方。此法行不通。我们需要研究一下,看有没有更好的解决办法。

我们考虑 c , d 两点的异或距离 

D = e2 ^ e3 

而我们知道,两个相同的数字异或结果为 0,而任何数字与 0 异或不变,所以 

D = e2 ^ e3 ^ x ^ x

我们令 x 等于 b 点到根节点的异或距离,

于是上式可以写为:

D = e2 ^ e3 ^ e1 ^ e1 = (e2 ^ e1) ^ (e3 ^ e1) = (c ,a 异或距离) ^ (d ,a 异或距离)

于是,可以得到结论,任意两个点之间的异或距离等于这两个点到根节点的异或距离的异或。这是一个很重要的结论。

于是,此问题就转换为了这样的一个问题,给出所有节点到根节点的异或距离,两两组合,有多少种组合使得它们的异或结果等于 K?

我们再考虑一下转换后的问题的时间复杂度,

1).得到所有节点到根节点的异或距离与次数对(即,记录一个值,且它出现的次数,表示有多少点到根节点的异或距离等于该值)的集合 S 时间复杂度为 n。

2).进行两两组合,使异或结果等于 K 的时间复杂度为 n * n。

所以时间复杂度为 n * n。其实第二步是可以优化的,借助于一个性质,a ^ b = c 则 a ^ c = b,于是,遍历 S中的元素si,且在已知 K 的情况下,求满足 si ^ x = K 且 x 属于 S 的 x 有多少个,就相当于求集合中 S 的元素 si ^ K 有多少个。于是,第二个步骤的时间复杂度就变为了 1.所以整个问题的时间复杂度为 n。

到此,第一个问题得到圆满解决。

2.问题2

此问题与上一个问题第一步是一样的,不同的在于第二个步骤,现在不能利用 a ^ b = c 则 a ^ c = b 这个特性了,因为现在的条件是小于。那么,是否只能进行两两组合探索异或结果是否小于 K 呢?这样做的时间复杂度为 n * n,让我们再思考有没有简单的办法。

从条件上入手,若 a ^ b < K ,则:

设 a ^ b 的结果的二进制形式为 s1, K 的二进制形式为 s2,从高位数起,s1 与 s2 总共有 x 位连续相等,那么,

s1 的第 x + 1 位必为 0,s2 的第 x + 1 位必为 1。那么,我们可以通过枚举 a 和 x ,让 x 从 0 到 31 之间变化,再根据 a 的值,确定有多少个 b 满足条件。这个问题可以转换为前缀树问题。前缀树如图2所示:


图2 前缀树

根节点上没有数值,其它所有节点的值为 0 或 1。每个叶子节点对应一个数字,比如,从左向右数,第一个叶子节点表示的二进制数字为 000 ,即 0;倒数第二个叶子节点的二进制数字为110,即6。

我们的思路是:

1).得到所有节点到根节点的异或距离与次数对的集合 S

1.根据 S 构成一棵前缀树。第一层为一个空的根节点。其它每个节点有这样的属性:{左节点指针,右节点指针,左右子树的叶子节点个数}

2.下标 i 遍历 S ,对于每个 si ,下标 j 遍历 0 到 31 ,设 j 为 si ^ x 与 k 的最长公共子位串,根据 si ,j , k 可反推 x。
举例:

设 si = 1010110 , k = 0110111

假设 j = 1,即 si ^ x 只有一位公共子串,所以,此时,x 的第一位必为 1 ,因为 si 第一位为 1 ,这样才能保证 si ^ x 的第一位与 k 的第一位相等。同理,因为 k 第二位为 1,要保证小于 k ,则 si ^ x 第二位必为 0,所以 x 的第二位为 0,

也即 x = 10... 所以,现在统计 x 以 10 开头的,且在 S 中的数字有多少个即可。

假设 j = 3,即 si ^ x 有三位公共子串,而 k 的第四位为 0,所以,si ^ x 的第四位必然也为 0 ,才能保证 si ^ x < k,所以,j = 3 不成立,不做处理,继续转入 j = 4,此时,si ^ x 的第五位必为 0,因此,x = 11001...,因此,现在统计 x 以 11001 开头且在 S 中的数字有多少个即可。

如此往复,将所有个数加起来,即是所求。不必担心,对于不同的 j 会造成重复计数,这是因为,每一个不同的 j ,

si ^ x 前 j + 1位都是固定的(前 j 位与 k 相同,后一位为 0 且与 k 的 j + 1 位相反,若中 k 的 j + 1 位为0则跳过该 j 不计算),这样, si ^ x 随着 j 的变化而变化,每一次随着 j 不同,都不同。由于 si ^ x 对于不同的 j 是不同的,所以,x 对于每次不同的 j 也是不同的,不必担心重复计数。

第2步其实是可以优化的,不需要遍历 0 到 31 ,我们只需要根据 k 的哪些位上为 1来遍历即可。如,k = 10110101010,那么,只需要假设 k 与 si ^ x 最长公共子位串长度为 0,2,3,5,7,9即可(即遍历 k 的二进制 bit,记下 bit 中哪些下标的值为 1,下标从 0 开始计)。得到这个数列之后,遍历每个 si ,可推知 x 有多少个。

至此,问题得到解决。


0 0
原创粉丝点击