【题解】Solution Set - NOIP2024集训Day17 整体二分
【题解】Solution Set - NOIP2024集训Day17 整体二分
https://www.becoder.com.cn/contest/5488
二逼平衡树 /【模板】树套树
rt. 树套树。
「国家集训队」矩阵乘法
整体二分板子。
如果每次二分的时候都 \(O(n^2)\) 重新算二维前缀的话,时间复杂度是 \(O(n^3\log n+q\log n)\)。非常卡(实际上没分)。
考虑记录每个元素的值和坐标,然后排序。
每次把权值在 \([l,mid]\) 内的加入到一个二维树状数组中,然后再查询。
因为:
- 每层每个点会且仅会被加入一次;
- 每层每个询问会且仅会询问一次;
每次修改和查询都是 \(O(\log^2 n)\),一共 \(\log n\) 层,所以总时间复杂度为 \(O((n^2+q)\log^3n)\)。
「BalticOI 2004」Sequence 数字序列
做过。
对于一个 \(b_i\) 如果在决定取 \(x,x+1\) 的时候,其实确定的是一种趋势。比如选择了 \(x\) 那么最终 \(b\) 的取值一定 \(\le x\)。
所以我们考虑整体二分。
注意到我们求出的 \(b\) 是满足的不减的。
输入时 \(a_i\gets a_i-i\),输出时再把 \(i\) 加回去,就可以保证 \(b\) 严格单增了。
「HNOI2015」接水果
一句话题意:
给定一棵树上的一堆路径,路径有权值。每次询问给定一个路径问这条路径的子路径权值第 \(k\) 小。
难点在于怎么判断水果 \((u,v)\) 是否包含盘子 \((x,y)\)。(不妨 \(dfn[u]<dfn[v]\wedge dfn[x]<dfn[y]\)。
一条路径 \((u,v)\) 包含 \((x,y)\) 的充要条件为 \(S\)。
分讨:(这个分讨其实感觉还挺难想到的,可以当成一个 trick 吧
\(\text{lca}(x,y)=x\)。(显然 \(y\) 一定是不能作为 \(\text{lca}(x,y)\) 的。
令 \(z\) 为从 \(x\) 到 \(y\) 路径上的第一个点。
那么:\[S\Leftrightarrow \begin{cases} dfn[u]\in[1,st[z])\\ dfn[v]\in[st[y],ed[y]] \end{cases} \vee \begin{cases} dfn[u]\in[st[y],ed[y]]\\ dfn[v]\in(ed[z],n] \end{cases} \]\(\text{lca}(x,y)\ne x\)。
那么:
\[S\Leftrightarrow \begin{cases} dfn[u]\in[st[x],ed[x]]\\ dfn[v]\in[st[y],ed[y]] \end{cases} \]
所以每个盘子可以视作一个矩形,水果视作一个点。
每次询问就是问包含这个点的那些矩形中权值第 \(k\) 大,离线整体二分,每次扫描线即可。
「HNOI2016」网络
每次询问,我们二分答案,check 的话就是看重要程度 \(> mid\) 的是不是都被影响。
直接对于所有 \(>mid\) 的路径 \(+1\),然后看这个故障点是不是被覆盖了这么多次。
然后直接整体二分就行了啊。
每次我们其实只需要判断 \([mid,r]\) 因为值域大于 \(r\) 的都已经判过是都被影响的了,这样就保证了时间复杂度。
如果直接树剖的话是 \(O(n\log^3 n)\) 的。
直接用一个 bit 维护子树和,然后每次差分修改。
这样单次路径修改就变成了 \(O(\log^2 n)\)。
总的时间复杂度为 \(O(n\log^2 n)\)。
「P5163」 WD与地图
先把所有操作倒过来,把断边转化为连边。
然后每个连通块维护一颗权值线段树,然后线段树合并就行了。
关于线段树合并的时间复杂度:
一次完整的线段树合并的时间复杂度 \(=\) 初始状态下叶子节点的个数 \(\times\) \(\log V\)(\(V\) 是线段树的值域。
Why?
对于最终状态的线段树上的每一个点,之前合并的时候每访问一次这个节点,一定是会合并两个叶子节点的。而且必须存在两个叶子节点来给她合并,也就是说每个节点至多会被访问的次数是其 子树的叶子节点的个数。
这样一来每层的访问次数总和就是叶子节点个数,而一共 \(\log V\) 层,所以结论成立。
所以,这道题线段树合并的时间复杂度整体是 \(O(n\log n)\) 的。
但是好像不需要整体二分?直接做就好了?
哦,原来是有向图啊。😅
有点难办,要维护强连通分量。
pyt 告诉我的一个很聪明的想法。
如果我们知道了每次询问前的强连通分量的状态,当然可以按照之前无向图的方法来解决。
其实,我们只需要知道「每一条边第一次两端同属一个强连通分量的时间」。
显然这个时间是不早于这条边加入的时间的。
所以我们就可以直接这个时间排序(如果这个时间相同,按原始时间排),按照无向图的方式,每次线段树合并,然后查询就行了。
现在就是要求解这个时间了。
都出现「第一次」了,所以这个时间一定是有单调性的。
考虑整体二分。
我们每次把原始时间小于等于 \(mid\) 的边及其两端的点单独拎出来,跑一遍 tarjan(这样就保证了时间复杂度)。
然后每一条边看其两端是否在同一强连通分量里面,就没了。
总结一下,听了解法之后,感觉挺对的。但是真的很难想到去求「每一条边第一次两端同属一个强连通分量的时间」这个东西。
关于实现:(其实也挺精妙的。
我们可以直接在每一次 solve
出口的时候,直接用并查集和线段树合并维护强连通分量(不需要撤销),这样每次不管是在算答案的时候,还是跑 tarjan 的时候(每条边的两个端点都重新赋值成其强连通分量内部的代表点)前面的边的贡献都是恰好对的。