Solution Set before NOIP2023

香蕉公司

题意:维护 \(n\) 的排列 \(a_0\)\(p\)。给定 \(q\) 次操作,第一种是交换 \(a_0\) 中两个值或 \(p\) 中两个值,第二种是比较 \(a_x\)\(a_y\) 的字典序大小,其中 \(a_k\) 的第 \(i\) 位是 \(a_{k-1}\) 的第 \(p_i\) 位。

\(n,q\le10^5\)\(x,y\le10^{18}\)

\(a_x\)\(a_y\) 第一次不相同的点为关键点。

显然维护置换环。可以用平衡树维护环上的顺序,然后注意到一个环上的编号最小点才有可能成为关键点。所以以每个环上最小点为下标建立线段树,维护的是环的大小的 \(\text{lcm}\)。那么查找时直接线段树上二分出关键点再检查即可。

P4497

链接

这个题的数据好像有不只一种合法解,所以按照出题人的意愿,差分数组为 \(0\) 的应该跟负数一起处理。

我们可以贪心地考虑到若 \(k\) 为奇数,则 \(B_k<B_{k+1}\)。所以题目可以转化为在差分序列上选择若干段和为正数的值,最后的值就是选出数的和,那么显然选且仅选正数是最优的。这是易于维护的。

考虑第二问,实质上就是在差分序列中,两段正数中间的负数段上选一个数删掉,然后加上其他负数。那么删最大值最优。所以对每个负数段 \(S\),记 \(f_S=\sum\limits_{x\in S}x-\max\limits_{x\in S}\{x\}\),那么用 set 维护连续负数段,用权值线段树维护 \(f\),查询的时候直接在权值线段树上二分即可。

显然包含开头或末尾的连续负数段不能产生贡献,另外注意差分序列在 \(2\)\(n\) 的范围内考虑。

P6545

链接

我们考虑怎么满足包住所有关键点的限制。我们可以从连一些边关键点连一些边到左上角,然后做最短路,要求不能穿过连的边。这个可以拆点解决。但是我们发现这样算出来很明显不是最优的。但是我们可以把连的边改成一条最短路,这样穿过它就是不优的,正确性就有了保证。

实现比较丑陋。

Ridiculous Netizens

题意:给定一棵有 \(n\) 个节点的树,每个点有一个正整数点权,求树上有多少个连通块权值之积不超过 \(m\)。答案对 \(10^9+7\) 取模。

\(1\le n\le2\times10^3\)\(1\le m\le10^6\)

考虑一个比较经典的 trick,使用点分治,将问题转化为一棵有根树,必须选根的方案数。直接 dfs 求解。令 \(f_{i,j}\) 表示仅考虑 dfs 序前 \(i\) 个点,乘积为 \(j\) 且必须选根的方案数。那么转移就可以由 dfs 序为 \(i-1\) 的点和爷爷作差得到。 因为钦定选根所以没有问题。但是这样是 \(O(nm\log n)\) 的,不如直接暴力。然而我们可以考虑将 \(j\) 变为 \(\left\lfloor\dfrac{m}{j}\right\rfloor\)。这样状态数就是 \(O(\sqrt m)\) 的了。并且这个也可以直接整除。总时间复杂度就变成了 \(O(n\sqrt m\log n)\)


update on 2023.9.25:之前贴了很多代码,感觉很丑陋而且很令人不适,所以删掉了。


BZOJ3120

题意:将 \(n\)\(m\) 列的矩阵染色,问有多少种染色方案使得每一行没有大于等于 \(p\) 个连续的 1,并且至多有 \(q\) 行全部为 1

\(1\le 8\le n\)\(1\le m\le10^{18}\)\(1\le p,q\le3\)

考虑到 \(n\) 很小而 \(m\) 很大,想到矩阵快速幂。我们可以一列一列地 dp,那么我们就需要记录当前一列的形态(前面有几个连续的 1)与全为 1 的行数。这样状态数是 \(O(p^nq)\) 的,总复杂度就是 \(O(p^{3n}q^3\log m)\),很显然寄了。

其实仔细想想我们可以不用考虑顺序,只需要考虑每种状态的数量,这样一来当 \(p=3\) 时状态数就是 \(O(n^2q)\) 级别的了。时间复杂度 \(O(n^6q^3\log m)\)。这题还特别卡常,可以在矩阵乘法的时候在当前位置是 \(0\) 时直接 break 掉。

CF1491H

链接

考虑这个问题在一个平凡的树上是不好做的,不同点在于这题始终有 \(a_i<i\)。我们可以考虑弹飞绵羊的做法,即分块,然后维护每个位置第一次跳出该块的位置 \(p_x\),那么这样查询就可以 \(O(\sqrt n)\) 做了,做法类似于树剖求 LCA。但是这样不好修改。注意到一个块的修改量如果大于块长,那么此时就一定有 \(p_x=a_x\)。也就是说修改也可以直接暴力改,并记录一下增加量,如果大于块长就直接打标记就可以了。总时间复杂度 \(O((n+q)\sqrt n)\)

栈的维护

题意:维护一个栈,支持插入一个值为 \(x\) 的元素或是弹出 \(x\) 个元素(没有那么多则看成弹空)。给定长度为 \(n\) 的操作序列,\(m\) 次修改其中操作,修改不独立。问每次修改完过后依次执行所有操作后栈中元素权值之和。

\(1\le n,m\le 2\times10^5\)

我们发现前面插入的操作是不会对后面插入的元素产生任何影响的。那么我们就可以考虑从后往前来处理。具体地,假如我们将操作分块,那么我们就可以处理出块内操作一次执行后会删去多少个之前的元素,以及这个块内新增元素(维护一个前缀和比较方便)。然后从后往前的时候就可以直接查询前缀和删掉后面一些的值,并维护一下从现在往后执行操作会删去多少个之前的元素(直接减去删这块的贡献,再加上这块维护的这个值)。那么复杂度就是 \(O(m\sqrt n)\)

这个题还有 \(O(m\log^2 n)\) 的做法,可惜我不会。/kk


update on 2023.10.11:把一些不便于公开的题加进了 hidden part


CF1610G

链接

首先有一个关键结论就是删去字符在原序列上的每个连续段都必须是一个可匹配的括号序列(叫合法括号序列吗?)。这一点可以通过不断调整一对被删除,但是中间没被删完的括号来证明。那么我们就可以考虑一个 dp。令 \(f_i\) 表示第 \(i\)\(n\) 个字符形成的最优的结果。那么转移只有两种情况:\(f_{i+1}\) 接上 \(s_i\),与 \(f_{p_i+1}\),其中 \(p_x\) 是与 \(x\) 匹配的右括号的位置(如果 \(s_x\) 是左括号就忽略)。注意正序 dp 是假的。但是要比较字符串,所以是 \(O(n^2)\) 的。我们发现比较字典序就是找到第一个不同的位置并比较这个位置的大小。所以说我们可以倍增地记录它删除后字符串的第 \(2^k\) 个位置,与这个位置所形成的前缀串的哈希值,比较的时候就可以快速找到第一个不同的位置并比较了。时空复杂度 \(O(n\log n)\)

CF652E

链接

这个题很简单,观察到边双缩点后一个边双连同分量内的点都是等价的,所以直接判断 \(a\) 所在边双到 \(b\) 所在边双的路径上是否有 1 边(点或者边上)就可以了。

这个题目还可以加强,那就是对于每个点求出其能够到达(存在一条含有 1 边的简单路径)的点数。这个也可以直接换根做(看的题解,没有仔细思考细节)。

但是有没有更简洁的形式呢?我们发现边双缩点完后是一棵树,这表示一个点不能够到达的点集是树上的一个连通块(假如把自身这个边双也考虑进去的话)。所以直接维护极大互不可达边双连通块。这就是用 1 边和 1 点把原树分割成森林。这样就可以简单地计算更多形式的东西了。

P7497

链接

我觉得这题评蓝是有点抽象的。但是多半是我太菜了。

首先只维护区间加区间乘是板子。但是有封锁元素的操作。我们可以考虑把它看成封锁标记的区间加减。那么每一次修改时只有封锁标记为 \(0\) 的位置可以产生贡献。那么我们可以维护封锁标记的最小值,以及这种位置的个数,和这种位置的元素和。这样做加法和乘法的时候都可以快速更新了。然而 pushdown 的时候乘法加法标记与封锁标记之间的下传顺序好像是有一些问题的。因为先解封再修改,和先修改再封锁,这两种情况的下传顺序是不同的。我们考虑规避这样的问题。容易发现,若对大区间修改操作产生贡献的位置都是最小值。设其为 \(x\),那么两个子区间中产生贡献的位置也是 \(x\)。我们就可以考虑维护每个节点在打懒惰标记之前封锁标记的最小值,并与儿子的封锁标记最小值比较,只有相等的时候才能下传给那个儿子。时间复杂度 \(O(n+m\log n)\)

P6632

链接。但是不考虑基环树,只考虑树。

为什么,我连这样的状态定义也想不到啊。

考虑 \(f_i\) 表示当 Alice 初始在 \(i\) 的父亲节点时,只考虑 \(i\) 的子树,Bob 至少先行动多少轮能使得 Alice 无法到达任意一个 \(T\) 内的点。很显然有两种转移方式,一种是直接选子树内离 \(i\) 最近的过来挡,另一种还是就各个子树互不影响,那就是所有 \(i\) 儿子节点的 \(f\) 的和,再减去 \(i\) 的父向边的边权(因为 Alice 从父亲赶下来的途中 Bob 还有这么多次移动的机会)。注意和 \(0\) 取最小值。

然后就结束了。

跳棋

题意:一个 \(n\times m\) 的棋盘,每个格子有三种状态,分别是空地,障碍,和棋子。Alice 先手,每次当前操作的人要移动一个棋子,将其移动到它的左边,右边或是下面,这个位置不能是障碍(但可以原来已经有若干棋子),并且这个棋子以前没有到过这个地方。每一行是环,即 \(1\) 左边是 \(m\)。不能操作的判负。问先手是否必胜。

\(1\le n,m\le10^3\)

首先考虑每个棋子的 \(\text{SG}\) 函数,然后将其异或起来。那么我们算一个位置的 \(\text{SG}\) 函数值,必须要知道它之前已经经过了哪些位置。容易发现我们只需要知道这一行它经过了哪些位置就可以了。并且容易发现这是一个区间。所以我们现在已经拥有一个 \(O(nm^2)\) 的做法了。

考虑优化。我们换一个定义,就是记录当前所在位置,以及只能向左走或者向右走。那么如果一列至少有一个障碍的话,我们是可以根据这个状态知道它什么时候不能继续在这一行移动。但是假如这一行没有障碍的话这样定义就有问题。我们发现实际上获取这一行每个位置是对下一行一段的 \(\text{SG}\) 值进行若干 \(\text{mex}\) 操作 (就是 \(\text{mex}(\text{mex}(\text{mex}(s_1,s_2),s_3)\dots)\) 之类的),用静态数据结构维护一下就可以了(具体地就是维护某一段,如果前面接了一段结果为 \(k\) 的,那么这一段最后的结果。因为所有状态的 \(\text{SG}\) 值不超过 \(3\),所以可以直接暴力维护)。时间复杂度 \(O(nm\log m)\)。但是好像也能直接线性维护。

gym102341B

链接

容易发现 \(F(i,j)\) 其实就是删点的最小割:最少删多少点使得不存在从 \(i\)\(j\) 的路径。那么我们可以直接设计状态。令 \(f_{i,S,j}\) 表示从第 \(i\) 层开始,目前可达的点集为 \(S\),并且还可以删 \(j\) 个点的情况下最早可以使哪一层不可达。转移就直接枚举删除的点即可。时间复杂度 \(O(nm^22^m)\)。注意到最后求得 \(f\) 后算答案也是容易的,就是直接用 \(f\) 转化就可以了。

QOJ107

链接

我们考虑一条链的贡献实际上要知道一个断点(即最长路径与链相连的那个点)。我们考虑枚举这个点。那么相当于我们要从这个点延伸三个部分,其中两个部分是链的端点,另外一个部分是最长距离。所以说我们只需要考虑以这个点为根出发不同儿子到某个叶子的前三长的路径。令其为从短到长为 \(x,y\)\(z\)。那么让 \(z\) 做距离,即让贡献为 \((x+y)z\) 是最优的。这样第一问就做完了。对于第二问我们也可以直接对每一个点,求它的答案。可以发现每种方案只会被算到一次。因为如果不是这样的话与 \(z\) 最大相互矛盾。那么随便维护一下就可以了。

posted @ 2023-09-11 08:50  TulipeNoire  阅读(36)  评论(0编辑  收藏  举报