您在 cmets 中写道,您当前的方法是
看来,我正在寻找一种方法来减少 O(n^2) 中的常数。我选择
一些顶点。然后我创建了两个集合。然后我用
部分总和,同时从这个顶点迭代到树的开始并到
树的完成。然后我找到集合交叉点并获取路径数
从这个顶点。然后我对所有其他顶点重复算法。
有一种更简单,我认为更快的O(n^2) 方法,基于所谓的两个指针方法。
对于每个顶点v,同时进入两个可能的方向。让一个“指针”指向一个顶点 (vl) 向一个方向移动,另一个 (vr) 向另一个方向移动,并尽量保持从 v 到 vl 的距离尽可能接近从 @987654330 的距离@ 到 vr 尽可能。每次这些距离相等时,您就有相等的路径。
for v in vertices
vl = prev(v)
vr = next(v)
while (vl is still inside the tree)
and (vr is still inside the tree)
if dist(v,vl) < dist(v,vr)
vl = prev(vl)
else if dist(v,vr) < dist(v,vl)
vr = next(vr)
else // dist(v,vr) == dist(v,vl)
ans = ans + 1
vl = prev(vl)
vr = next(vr)
(通过预先计算前缀和,您可以在 O(1) 中找到dist。)
很容易看出,只要你没有零长度的边,就不会错过任何相等的对。
关于更快的解决方案,如果你想列出所有对,那么你不能做得更快,因为在最坏的情况下,对的数量将是 O(n^2)。但是,如果您只需要这些对中的 数量,则可能存在更快的算法。
UPD:我想出了另一种计算数量的算法,如果你的边缘很短,它可能会更快。如果您将链的总长度(所有边权重的总和)表示为L,则算法在O(L log L) 中运行。但是,它在概念上更先进,在编码方面也更先进。
首先是一些理论推理。考虑一些顶点v。让我们有两个数组,a 和 b,不是 C 风格的零索引数组,而是索引从 -L 到 L 的数组。
让我们定义
- 对于
i>0, a[i]=1 iff 到v 的右边 正好在i 的距离上
是一个顶点,否则a[i]=0
- 为
i=0、a[i]≡a[0]=1
- 对于
i<0,a[i]=1 iff 到 left 的 v 的距离正好在 -i 有一个顶点,否则 a[i]=0
对这个数组的简单理解如下。拉伸图形并将其沿坐标轴放置,使每条边的长度等于其权重,并且顶点v 位于原点。然后a[i]=1 iff 在坐标i 处有一个顶点。
对于您的示例和选择为v 的顶点“b”:
a--------b--c--d--e
--|--|--|--|--|--|--|--|--|-->
-4 -3 -2 -1 0 1 2 3 4
a: ... 0 1 0 0 1 1 1 1 0 ...
对于另一个数组,数组b,我们以相对于原点对称的方式定义值,就好像我们颠倒了轴的方向:
- 对于
i>0, b[i]=1 iff 到 v 的 left 的距离正好在 i 那里
是一个顶点,否则b[i]=0
- 为
i=0,b[i]≡b[0]=1
- 对于
i<0, b[i]=1 iff 到v 的右边 距离正好-i 有一个顶点,否则b[i]=0
现在考虑第三个数组c,这样c[i]=a[i]*b[i],星号在这里保留用于普通乘法。显然c[i]=1 iff 长度为abs(i) 到左侧的路径以顶点结束,而长度为abs(i) 的右侧路径以顶点结束。因此,对于i>0,c 中具有c[i]=1 的每个位置都对应于您需要的路径。还有负位(c[i]=1和i<0),只是反映正位,还有一个位置c[i]=1,即位置i=0。
计算c 中所有元素的总和。这个总和将是sum(c)=2P+1,其中P 是您需要的路径总数,v 是它的中心。所以如果你知道sum(c),你可以很容易地确定P。
现在让我们更仔细地考虑数组a 和b,以及当我们更改顶点v 时它们如何变化。让我们将v0 表示为最左边的顶点(树的根),而a0 和b0 表示该顶点对应的a 和b 数组。
对于任意顶点v 表示d=dist(v0,v)。然后很容易看出,对于顶点v,数组a 和b 只是数组a0 和b0 移动了d:
a[i]=a0[i+d]
b[i]=b0[i-d]
如果你还记得那张树沿着坐标轴伸展的图片,那就很明显了。
现在让我们再考虑一个数组S(所有顶点的一个数组),对于每个顶点v,让我们将sum(c)的值放入S[d]元素(d和@ 987654411@ 依赖于v)。
更准确地说,让我们定义数组S,这样对于每个d
S[d] = sum_over_i(a0[i+d]*b0[i-d])
一旦我们知道S 数组,我们就可以遍历顶点,并且对于每个顶点v 获得它的sum(c),就像S[d] 和d=dist(v,v0),因为对于每个顶点v,我们都有@987654422 @。
但是S 的公式非常简单:S 只是a0 和b0 序列的convolution。 (公式不完全符合定义,但很容易修改为精确的定义形式。)
所以我们现在需要给定a0和b0(我们可以在O(L)时间和空间中计算),计算S数组。之后,我们可以遍历 S 数组并简单地从 S[d]=2P+1 中提取路径数。
上面公式的直接应用是O(L^2)。但是,通过应用快速傅里叶变换算法对两个序列can be calculated in O(L log L) 进行卷积。此外,您可以应用类似的Number theoretic transform(不知道是否有更好的链接)仅处理整数并避免精度问题。
所以算法的大致轮廓就变成了
calculate a0 and b0 // O(L)
calculate S = corrected_convolution(a0, b0) // O(L log L)
v0 = leftmost vertex (root)
for v in vertices:
d = dist(v0, v)
ans = ans + (S[d]-1)/2
(我称之为corrected_convolution,因为S不完全是一个卷积,而是一个非常相似的对象,可以应用类似的算法。此外,你甚至可以定义S'[2*d]=S[d]=sum(a0[i+d]*b0[i-d])=sum(a0[i]*b0[i-2*d]),然后S'是卷积本身。)