【题解】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]\) 内的加入到一个二维树状数组中,然后再查询。

因为:

  1. 每层每个点会且仅会被加入一次;
  2. 每层每个询问会且仅会询问一次;

每次修改和查询都是 \(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 吧

  1. \(\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} \]

  2. \(\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 的时候(每条边的两个端点都重新赋值成其强连通分量内部的代表点)前面的边的贡献都是恰好对的。

posted @ 2024-08-29 09:18  CloudWings  阅读(11)  评论(0编辑  收藏  举报