图上 DS 刷题笔记
希望能在 11.15 前刷完 CF 上 \(2800 \sim 3400\) 的图论 + DS 题。
自己切掉的题都在这个题单了,建议收藏这个动态更新超水紫黑题的题单!!111(题单还包含一些 trees + DS 的 \(2800 \sim 3400\),这部分的做题笔记受到密码保护,密码可以私信找我要。)
CF1566G Four Vertices
开门红。
最优方案不可能包含四条及以上的边,一个四条边的方案一定可以通过调整端点删去一条边。
如果我们选择了三条边,则一定是一个点的三条出边,否则可以被两条边替代。这种情况容易维护:对每个点维护前三小出边,再全局开一个 set 维护每个点的前三小边的和。
剩余情况都是两条边。直观的想法是找到最小和次小边 \((a,b),(c,d)\)。如果 \(a,b,c,d\) 互不相同则直接结束,否则设 \(d = a\),两条边分别为 \((a,b),(a,c)\)。
- 重要观察:\((a,b),(a,c),(b,c)\) 中至少有一条边被选择。
- Proof:假设我们已经选择了一条边 \((u,v)\)。如果 \((u,v) \ne (b,c)\),则 \((a,b),(a,c)\) 两条边至少有一条可以选。由于它们是最小的两条边,故一定会选。
现在问题变成,维护边集 \(\{(u,v,w)\}\),每次查询给出 \((a,b)\),找到一对 \((u,v,w)\) 满足 \(a,b,u,v\) 互不相同且 \(w\) 最小。
将所有边挂在较小端点 \(u\) 上,设询问的 \(a < b\)。将左端点拆分成三段区间 \([1,a),(a,b),(b,n]\),每个区间内查一个 \(w\) 的右端点 \(v\) 满足 \(v \ne a,v \ne b\)。线段树每个节点维护 \(w\) 前三小的 \(v\) 即可。
时间复杂度 \(\mathcal O(n \log n)\),常数非常非常大。实现时请注意常数,注意常数,注意常数,不是所有单 \(\log\) 都能轻松过 \(10^5\)。
CF1419F Rain of Fire
首先二分答案 \(mid\),将点合并成若干个连通块。考虑将每个点向上下左右四个方向引一条长度为 \(mid\) 的线段,不难发现我们需要考虑的点只有两种:
- 对于两个横/纵坐标相同的点,如果其距离小于等于 \(2mid\),且这两个点恰好属于这张图仅有的两个连通块,那么我们可以在线段的交点处放点。
- 否则,相交方式一定是垂直相交。我们在交点处放点。
交点显然只有 \(\mathcal O(n^2)\) 个。可以预处理出每一个交点到旁边四个点的距离以避免二分里面再带 \(\log\)。
总复杂度 \(\mathcal O(n^2 \log n)\),但是会被卡常。最有用的卡常是:
if(time > 1900) {
cout << -1;
return 0;
}
CF1056G Take Metro
注意到在环上走 \(t\) 步和 \(t \bmod n\) 步没有区别,因此暴力做掉 \(t \bmod n\) 步后,剩余的是走 \(\dfrac{t}{n}\) 个 \(n - 1,n - 2,\ldots,0\)。这一部分显然可以倍增处理。
问题是怎么对 \(1 \sim n\) 的每一个 \(i\) 都求走 \(n\) 步后会到哪里。注意到移动方式很简单,从 \(i\) 到 \(i-1\) 就是一些简单的复制拼接。这里需要上 DS 了:区间复制,区间移动,需要使用可持久化平衡树完成。本题可以直接用 rope 水过。
CF757F Team Rocket Rises Again
套路的建出最短路 DAG,问题变为 DAG 上删一个点使得和 \(s\) 不连通的点数最多。
【支配树】可以做到对每一个 \(u\) 都求出删去它后和 \(s\) 不连通的点数,DAG 上建立支配树的方法如下:
- 称 \(u\) 支配 \(v\) 当且仅当所有 \(s \to v\) 的路径都经过 \(u\)。任意两个支配 \(u\) 的点 \(x,y\) 一定呈支配关系(如果 \(x\) 支配 \(u\) 但 \(x,y\) 无支配关系,那么会存在路径 \(1 \to x \to u\) 而不经过 \(y\))。
- 把每个点和最近的支配它的点连边得到支配树。
- \(u\) 支配 \(v\) 的充要条件是对于 \(v\) 的所有入边 \((w,v)\),\(u\) 都支配 \(w\)。
- 结合以上不难发现 \(u\) 的支配父亲就是支配树上 \(w\) 的 LCA。
动态加叶子维护 LCA 可以倍增完成。
CF1654G Snowy Mountain
思路打开,着眼全局。
首先很容易发现的是,反复横跳只会在最后一条等边处发生,调整易证。
然后我花了两个小时也没想到题解一句话带过的结论:
- 结论:设 \(u\) 能到达的 \(f_v\) 最小的可以反复横跳的点是 \(v\),答案是 \(2f_u - f_v\)。
- Proof:考察从 \(u\) 一直到 \(v\) 反复横跳完所有动能的整个过程,一定走了 \(2f_u - 2f_v\) 步。加上剩下的一段路即为 \(2f_u - f_v\)。
对每个点求 \(v\) 只需要点分治即可。
CF1470E Strange Permutation
首先注意到一种方案里对字典序影响更大的是最开始的修改,不妨对每个询问都先考虑其第一个修改在哪里。将所有可能的位置排序,然后对每一个第一个修改,计算出其后面修改的方案数,然后二分找到那个对应的修改后,递归入一个后缀的子问题。
由于 \(c \le 4\),递归层数最多只有 \(4\) 层。离线所有询问,对询问扫描线。从后向前扫,一个一个后缀往进插入,用线段树二分查询询问。复杂度 \(\mathcal O((n + q) c \log n)\)。
调了一上午。太太太难写了啊啊啊啊啊啊。
CF763D Timofey and a flat tree
子树同构直接树哈希,求每个根直接换个根就好了。
CF798E Mike and code of a permutation
trick:图的拓扑序可以对反图 dfs 求出,参考代码:
vector<int> order;
void dfs(int u){
visit[u] = 1;
for(int v : u 的反图出边)
if(!visit[v]) visit[v] = 1,dfs(v);
order.push_back(u);
}
这样做的好处类似 dj 存边的最短路,每个点只会被遍历一次。对于 \(\mathcal O(n^2)\) 条边的 dag,我们只需要快速找到一个 \(u\) 的反图出边,在数据结构里把这个点删掉,复杂度就可以做到 \(\mathcal O(nf(n))\),其中 \(f(n)\) 是数据结构的复杂度。
本题很容易想到暴力 \(\mathcal O(n^2)\) 的暴力拓扑做法,考虑应用上面的 trick。设 \(b_i\) 是点 \(i\) 被标记的时间(\(b_{a_i} = i\)),则所有 \(b_i > x,i < a_x\) 的 \(x\) 都比 \(i\) 小。使用线段树维护反图即可。
CF1615H Reindeer Games
保序回归。
- 引理:如果代价函数 \(f(x)\) 是凸的,如果全局最优解是 \(b_1,b_2,\ldots,b_n\),那么如果值域是 \([l,r]\) 的最优解是 \(b_1',b_2',\ldots,b_n'\) 满足 \(b_i' = \max(l,\min(r,b_i))\)。
- 证明:集训队论文 2018。
保序回归问题的通常做法是整体二分。根据上面的引理,我们限定值域为 \([mid,mid+1]\) 即可得知全局最优解是 \(\le mid\) 还是 \(> mid\),然后二分下去即可。判断每个位置取 \(mid\) 还是 \(mid + 1\) 可以用最大权闭合子图判断。复杂度未知。
CF1580E Railway Construction
观察到了所有的性质,但是差了一点基本 DS 能力。时光倒流,总是想不起来啊。
首先不在最短路 DAG 上的边显然可以直接忽略,然后考虑如何刻画“存在两条不交的最短路这个条件”,考虑 DAG 的支配树,这一条件等价于 \(u\) 的支配点是 \(1\)。根据支配树的性质,点 \(u\) 的父亲是其所有入点的 LCA,也就是说,如果 \(u\) 存在两个不同子树的入边,那么 \(u\) 的父亲就是 \(1\)。由于 \(u\) 的任意两个入边经过操作后都会直接和 \(1\) 相连,因此,只要 \(u\) 的度数不为 \(1\) 其在支配树上的父亲就一定是 \(1\)。如果 \(u\) 的度数是 \(1\),那么其在支配树上的父亲不是 \(1\) 时需要连一条边。
至此得出一个点 \(u\) 合法的充要条件;入度大于 \(1\) 或者直接和 \(1\) 相连。
那么 \(q = 0\) 就解决了:直接按照最短路 DAG 上 \(d_u\) 升序遍历点,对于每个度数为 \(1\) 的点,其可以连接任意除了 \(pre_u\) 的点。维护前缀最小值和次小值,如果最小值不是 \(pre_u\) 就取最小值,否则取次小值即可。时间复杂度 \(\mathcal O(n)\)。
考虑有修改的情况,注意到修改只有增加,形式比较特殊。增加一个点的点权会让最小值连续段分裂,但是减少只会让最小值合并。因此考虑时光倒流。
考虑 set 维护最小值连续段和次小值连续段。如果对某个非最值进行了修改,那么考察它造成的影响:一定是把某一段最小值推平,同时把次小值赋值为最小值;并把后面一段的次小值推平。注意到第一种操作遍历 \(2s\) 个连续段并推平掉了 \(s-1\) 个,因此复杂度均摊下来正确,可以在这期间动态维护答案。
考虑如果修改了某个前缀最值怎么办,如果前缀最小值被修改,那么遍历的第一个最小值 set 连续段里的次小值连续段不会被推平,这样复杂度会假掉。因此这里我们不可以暴力遍历次小值的连续段,但是由于最小值是相等的,可以分讨考虑贡献。
最终的复杂度是 \(\mathcal O(n \log n)\),常数看起来挺大的。
好难写,摆烂。
CF1548E Gregor and the Two Painters
统计连通块的常见思想:代表元思想。
考虑对于每个连通块,在其代表元处进行统计,一个连通块应当有唯一的代表元。这样我们只需要统计有多少个点无法到达其它代表元。
为了迎合 \(a_i + b_j \le x\) 的限制,我们将代表元选在 \(a_i + b_j\) 最小的点(如有多个,选最靠左上角的)。这样我们可以快速判断一个点是否可以走向其它代表元:如果 \((i,j)\) 可以走到 \((i,k),k \in [l,r]\),那么要求 \(b_k \ge b_j\),调整易证,行同理。
不难想到预处理 \(p_i\) 表示第 \(i\) 行向两侧第一个小于它的行走的过程中 \(a_i\) 的最大值,\(q_i\) 表示列。这样 \((i,j)\) 合法的充要条件就是 \(a_i + q_j > x,b_j + p_i > x\),二维数点即可。
整个题的核心就是最开始的代表元思想,合理的选择一个代表元会使得这个题直接被秒掉。