250217 模拟赛 T2 连通块计数 题解
题意
有一个树有 \(n\) 个节点,其 \(\operatorname{dfn}\) 序由以下规则求出:
- 设置时间戳 \(\operatorname T = 0\),将树转为以 \(1\) 为根的有根树。并将每个节点的儿子按照节点标号从小到大排序。
- 访问节点 \(1\)
- 当前在访问节点 \(u\),将 \(\operatorname T\leftarrow \operatorname T+1\),然后将 \(dfn[u]\) 设置为 \(\operatorname T\)。
- 按照节点编号顺序访问 \(u\) 的儿子,重复操作 \(3\)。
求出 \(\operatorname{dfn}\) 序之后,有 \(q\) 个询问形如 \(k,l_1,r_1,l_2,r_2\cdots\),记点集 \(S=\{x\mid \exists\ i,dfn[x]\in [l_i,r_i]\}\),也就是 \(\operatorname{dfn}\) 序在任意一个区间里的节点。
保证 \(l_i,r_i\) 不交,且端点递增。
你需要求出 \(S\) 点集分为多少个连通块,输出连通块数 \(+1\)。
部分测试点强制在线。
\(n,m,\sum k\le 5\times 10^5\)
题解
考虑经典 trick:\(nxt\) 数组:\(nxt[u]=dfn[u]+sz[u]\),即 \(\operatorname{dfn}\) 序向后第一个不在 \(u\) 子树内的节点。
容易发现节点 \(u\) 在 \(\operatorname{dfn}\) 序上向后到达的第一个不连通的节点就是 \(nxt[u]\)。每在 \(nxt\) 上跳一次就会额外产生一个连通块。我们关注 \(u\) 在 \(nxt\) 上跳了多少次(倍增解决),以及每次的连通块有没有和之前的区间连边。
通过画图发现,一段连续的 dfn 区间是一些挂在左链右边的连续子树。只有左链上的节点才会和当前区间连边。左链从上到下 dfn 序单增,右边挂的子树从上往下 dfn 序单减。我们依次处理每个区间 \([l_i,r_i]\),可以不断记录落在当前左链上的所有区间 \([l_j,r_j]\),使用倍增判断出每一个区间都覆盖了多少棵 \([l_i,r_i]\) 挂在下面的子树,就可以知道它们使连通块数量减少了多少。
那么如何记录这些可能出现在左链上的区间呢?注意到区间对应的左链是从左向右转动的。如果当前区间 \([l_i,r_i]\) 把之前的某一个区间 \([l_j,r_j]\) 完全盖在了左边,那么 \([l_j,r_j]\) 就不可能对后面的区间产生贡献了。注意到每次盖住的区间是当前有效的所有区间的一个后缀,可以使用单调栈维护这些有效的区间。每次倍增跳 \(nxt\),数出 \([l_i,r_i]\) 有多少个子树被覆盖在了栈顶区间内。若完全覆盖栈顶区间,则弹栈。
AC 代码
注意细节。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 |
|