18th CCPC浙江省赛 D. Shortest Path Query
条评论题意
思路
首先,题目给定了每一条边$(u,v)$都满足二进制下表示下,$u_{(2)}$是$v_{(2)}$的一个前缀,如$u=10_{2}$,$v=101_{2}$。
由此,我们考虑构建完全二叉树这样的一个结构,则在以点$u$为根节点的子树里的任意一点$v$,均满足题目所给的前缀的条件。
为了更快的求出某点是否为子树中的一点,我们预处理一下每个点的父亲fa[now][0/1/.../20]
。
数组第二维分别代表每个点的第$0,1,\dots,20$个祖先(第$0$个祖先为它本身)。
而且,此处为完全二叉树,往上的祖先不会超过$20$个(实际上$17$个就好了,因为$2^{17}=131072>100000$,$20$个只是凑个整)。
最后,我们预处理每个点的深度dep[now]
,可以得到:
当且仅当(nxt >= now) AND (fa[nxt][dep[nxt] - dep[now]] == now)
时,满足nxt
是now
的子树中的一个节点。
预处理时间复杂度$O(nlogn)$。
然后,如果$u,v$两点是可达的,那么从任一点出发,都至少能够到达它们中的某一个公共祖先。
因此,在每一次询问中,我们可以考虑先找到他们的最近公共祖先。由于我们预处理了父亲数组fa[now][20]
,因此可以写倍增来求$LCA$。不过对于完全二叉树,我们还可以求他们二进制中的最长公共前缀,即为对应的$LCA$。
如:$u=15=1111_{(2)}, v = 12 =1100_{(2)}, LCA(u, v)=11_{(2)}=3$
后面发现写这种还不如写倍增LCA,太蠢了,不过如果用Trie树应该就很方便了吧…
然后我们研究一下,如何求出公共祖先到$u,v$中某一点的距离。
最初的时候,写了一个优雅(但是错的一塌糊涂,甚至套了二分试图降低复杂度)的$DP$:
1 | int search(int u, int v){ |
这段代码的实现的是求dis[pos][level]
的值,dis[pos][0]=0
,dis[pos][1]
为到达点$pos$的父亲的距离的最小值,dis[pos][2]
为到达点$pos$的父亲的父亲的最小值,以此类推。
但这段代码有致命的缺陷——不能够求解先往下再往上的情况(如:$2 \rightarrow 5 \rightarrow 1$)
HACK数据①
1 | 7 6 |
因此,更换思路。
考虑每个点跑一次最短路?直接对每个点跑迪杰斯特拉最短路的话,使用堆优化,得到每个点到其他点的最小距离的复杂度在$O(n^2logn)$左右,如果跑$Floyd$连数组都开不下,考虑缩小点集的范围。
我们前文说到,两个点互达,必需要经过它们的其中一个公共祖先,由此我们考虑,能不能跑每个点到达它们子树中每一个点的最短路?
分析复杂度,设极限数据为最大深度$maxdep=20$(此处 定义根节点$dep=1$)的完全满二叉树。
最后一层有$2^{maxdep - 1}$个点
每个点的子树大小为$1$(包括本身),则执行次数约为$2 ^ {maxdep-1} \times (1 * log (1) )$
- 同理可推,倒数第二层有$2^{maxdep-2}$个点,子树大小为$3$(包括本身),则执行次数约为为$2^{maxdep-2} \times (3 * log(3))$
我们整合一下,得到总的操作次数为:
$\sum_{i=0}^{maxdep - 1} 2^i \times (2^{maxdep - i} - 1) \times log(2^{maxdep - i})$
$=\sum_{i=0}^{maxdep - 1} (2^{maxdep} - 2^i) \times (maxdep - i)$
$\leq (maxdep - 1) \times ((maxdep - 1) \times 2 ^ {maxdep} - \sum_{i=0}^{maxdep - 1} 2^i))$
$=(maxdep - 1) \times ((maxdep - 1) \times 2^{maxdep} - 2^{maxdep} + 1)$
$\leq 2^{maxdep} \times (maxdep - 1) ^ 2$
$\approx nlog^2n$
发现数量级在$10^7$,可以通过该题,也就是说我们只需要对每个点跑一次最短路,计算某个子树的根节点到子树中所有点的最短路径就可以AC该题。
HACK数据②
顺便提供两组HACK数据:
1 | 7 11 |
1 | 15 18 |
代码
1 | /* vegetable1024 | liushan.site | Maybe Lovely? */ |
对拍器
中途因为各种BUG没调出来,上网找了个$std$用来对拍,顺便把对拍的代码都放在这里,希望对读者有用…
fcc.cpp
1 | //fcc.cpp |
maker.cpp
1 | //maker.cpp |
另外两个程序是$std.cpp$(上网自寻)和$my.cpp$(自己的代码),然后编译后放到同一目录就可以了。