2021.2 做题记录 part.1

P1912 [NOI2009] 诗人小G

考虑 dp,用 \(f[i]\) 表示在 \(i\) 出换行的最小代价

\[f[i]=\min\{f[j]+|suml_i-suml_j+i-j-1-L|^p\} \]

发现并不太好搞斜率优化因为 \(p\) 很大
观察,显然具有决策单调性
考虑决策单调性的流程,用一个队列储存
每次从队头取出第一个合法答案,计算 \(i\) 的值
然后推入队尾,依次比较,弹出不合法的
对于合法的,二分找到第一个优于队尾的位置,然后更改
推入队尾
\(\mathcal O(n\log n)\)
code

P4767 [IOI2000]邮局

首先考虑暴力的 dp
\(f[i][j]\) 表示前 \(i\) 个,修了 \(j\) 个, \(i\) 必须修的答案
那么转移是 \(f[i][j]=\min\{f[k][j-1]+cost(k+1,i)\}\)
但是注意这里的 \(cost\) 不满足四边形不等式
要证
\(cost(i,j)+cost(i+1,j+1)\leq cost(i,j+1)+cost(i+1,j)\)
只需证
\(cost(i,j)-cost(i+1,j)\leq cost(i,j+1)-cost(i+1,j+1)\)
既证
\(f(j)=cost(i,j)-cost(i+1,j)\)
单调不增
但是我们发现这个东西貌似是不一定的
考虑换一种 dp
\(f[i][j]\) 表示前 \(i\) 个,修了 \(j\) 个,每次考虑在 \(k,i\) 之间修
那么这个时候的 \(cost\) 就满足四边形不等式了
因为现在这个 \(cost\) 是从两边往中间走,随着 \(j\) 走的越远,我们左边往中间走的距离就越来越远,当 \(cost(i+1,j)\) 时相当于少走了很多
但是对于上面那个 \(cost\) 我们是从中间往两边走,\(i,j\) 的中点和 \(i+1,j\) 的中点之间可能有的时候有值,有的时候没有值,所以不满足单调性,相应的,也就不满足四边形不等式。
所以我们对于第二种 dp,我们把它看成一个类似于区间 dp 的过程,进行四边形不等式优化
复杂度 \(\mathcal O(n^2)\)
code

P5416 [CTSC2016]时空旅行

所有时空相当于构成了一棵树
考虑对于树求一个dfn,那么一个点存在的区间就是连续的好几段了
考虑线段树分治,把所有星球插入到线段树上
考虑如何查询
对于一个询问 \((s,x_0)\),本质上是询问对于所有的存在于 \(s\) 的星球中的点的

\[(x-x_0)^2+c \]

的最小值,拆开之后显然是一个凸包的形式。
在线段树上维护一个 \((x_i,x_i^2+c_i)\) 的下凸包
然后每次查询时空即可
因为我们需要按 \(x_i\) 顺序插入,所以最开始把所有时空的星球离线,排序后插入
同时为了让复杂度保持在一个 log,我们把所有询问排序,按照斜率 \(x_0\) 排序,然后再查询
这样就可以保证复杂度是 \(\mathcal O(n\log n)\)
code

CF930E Coins Exhibition

状态比较好想,我们用 \(f[i][0/1/2]\) 表示离散化之后的第 \(i\) 个位置,在 \(i-1\sim i\) 这一部分全是正的/反的/正反都有
为了方便讨论,我么把所有 \(l\) 减一,即所有要求变成左开右闭的形式
那么我们注意到,发生矛盾的条件就是一段 0 包含了一个 1 的要求,或者一段 1 包含了一个 0 的要求,那么这样一定是不合法的
现在我们需要转移 \(f[i][0]\),考虑在 \(i\) 之前的位置,哪些位置能够转移过来
\(L\) 为完整包含在 \([1,i]\) 里面的要求有 \(1\) 的询问中,左端点最大的那个数是多少
那么可以得到转移

\[f[i][0]=\sum_{j=L+1}^{i-1} f[j][1]+f[j][2] \]

同理我们可以得到 \(f[i][1]\) 的转移公式
对于 \(f[i][2]\),他可以任意转移,即

\[f[i][2]=(f[i-1][0]+f[i-1][1]+f[i-1][2])\times(2^{p_i-p_{i-1}}-2) \]

这样我们就可以在 \(\mathcal O(n\log n)\) 的时间内解决了
这种思路确实比较新颖(
code

P3777 [APIO2017]考拉的游戏

一道非常有意思的交互题
用这个颓废了一下午

subtask 1

随便一个位置放 1,其他的地方是0,最后没有被选的那个位置就是答案

subtask 2

要求 4 次找到最大值
过程不唯一
首先我们把所有位置变成 1,这样可以确定出 \([51,100]\) 的位置
然后把所有 \([51,100]\) 的位置上填上 2,可以确定出 \([76,100]\) 的位置
把所有 \([76,100]\) 的位置上填上 4,可以确定出 \([92,100]\) 的位置
把所有 \([92,100]\) 的位置上填上 11,可以确定出 \(100\)

subtask 3

考虑把 0,1 位置上都填上一个数 \(k\)
那么有可能一个被选了,一个没被选,这个时候被选的那个肯定更大
考虑如果两个都被选了,我们可以考虑扩大 \(k\) 的范围
如果两个都没被选,我们把 \(k\) 变小

发现 \(k\) 最大只能到 8,并且具有单调性,所以可以二分
但是正常的二分需要分4次,拿不到全部分数
所以需要魔改一下二分

subtask 4

发现 \(100\log 100 < 700\),所以问题变成一次操作比较两个数的大小,然后套上一次归并排序就可以了
发现这里的特殊限制是 \(W=200\),所以我们把比较的两个位置分别填上100,看哪个位置被选了即可

subtask 5

可以套用 sub3 的方法,大概能拿到30分左右

正解考虑一个分治,有点类似于 sub2 的做法

我们对于一个区间 \([l,r]\),我们找到一个合适的值 \(w\),让这一区间里的所有数填上 \(w\),最后如果能把这个区间变成两个小区间,就可以分开成两个小区间继续做
因为每个位置只会被比较一次,所以一共只会调用 99 次函数
考虑 \(w\) 的求解。

我们发现复杂度非常充裕,所以暴力枚举
判断合法的时候,我们只需要假设这一区间内的数是有序的,然后类似于交互库的判断方式,给出相应的 \(R\) 数组,只需判断 \(R\) 是否不同即可
这一部分的代码可以直接粘交互库

code

P4585 [FJOI2015]火星商店问题

首先考虑,没有时间上的限制的时候,如何快速的求区间与一个数的 \(xor\) 的最大值
显然只需要建立可持久化 01trie 即可。

考虑有时间轴的时候如何解决
只需要对于时间轴建立一个线段树,然后把所有修改插入到根节点到叶节点的路径上的每一个点
然后把询问插入到对应子区间,然后线段树分治。
在递归到每一个点的时候,暴力重构出一棵可持久化 trie
为了保证复杂度,我们需要离散化之后再重构,只需要构建该节点有的修改的点即可

考虑这样做的复杂度,每个修改会被做 \(\mathcal O(n\log n)\)
每次插入 trie 的时候还需要一个 \(\mathcal O(\log w)\)
所以最后的复杂度就是 \(\mathcal O(n\log n\log w)\)

code

P4151 [WC2011]最大XOR和路径

考虑这个过程,我们发现我们可以把路径看成是任意一条 \(1\to n\) 的路径,和许多许多环的异或起来
为什么这样是对的呢
考虑如果 \(1\to n\) 有不止一条路径,而我们任意找的那条路径不是最优的话,那么显然存在一个环包含 \(1,n\),此时我们就可以找到更小的那个
当然也有可能突然绕到外面去,此时我们发现连接链和环中间部分的路径被走了两次,相当于没有贡献。
所以做法就是把所有的环找出来,然后插到线性基里,最后查询。

关于找环,我们可以只考虑 dfs 搜索树上的和该点往上走的路径的部分组成的环,因为两个这样的环异或起来就是下面那个环
这样可以在 \(\mathcal O(n\log w)\) 的时间里解决

code

CF938G Shortest Path Queries

如果没有修改只有询问就是楼上那道题
但是有修改,我们依然还是考虑线性基
但是线性基没法撤销
所以我们可以用线段树合并,把插入和删除对应成时间轴上的区间修改
但是我们现在不能直接按上一道题的做法,先找一棵搜索树出来,因为树上的边可能也会删掉。
所以我们考虑一个可以撤销的并查集,维护每个点到该联通块的根的路径的异或和
使用按秩合并的方式,可以做到 \(\mathcal O(\log n)\)
这样的话,我们每次递归到一个节点,我们就把所有新建的边放到并查集里
如果这两个点之前就已经联通了,那么相当于是一个环,我们扔到线性基里
等回溯的时候,我们把之前所有连边在并查集上撤销掉
因为线性基不能撤销,所以我们直接全部删除,每个点直接把父节点复制一下

这样的话,线段树合并一个 log,并查集一个 log,线性基一个 log,最后的复杂度是 \(\mathcal O(n\log^2n\log w)\)
注意坑点,在递归到线段树叶子节点的时候,也要把之前的给撤销掉,否则 WA 死调不出来

code

P2962 [USACO09NOV]Lights G

考虑一个异或的高斯消元。
如果啥都不特判能拿88分。
但是这个做法是有问题的。
为什么呢?
因为有可能我们消元的过程中消出来了自由元
自由元就是消元过程中,当 \(a_{i,i}=0\) 的时候,这个时候有可能无解或者无数解。
好在这里每个点的答案只有 \(0,1\) 两种情况
所以我们暴力枚举自由元的值
复杂度是 \(\mathcal O(n2^x)\)\(x\) 是自由元的个数。
考虑这个做法的实现细节
最后消出来的矩阵一定只会在右上的半个矩阵中有数,所以我们需要倒序枚举自由元
以及别忘了初值 \(a_{i,i}=1\)

code

CF24D Broken robot

容易推出状态转移方程
特判掉 \(m=1\) 的情况
我们对于每一行进行高斯消元
这样的话做法是 \(\mathcal O(n^4)\)
观察消元的矩阵,发现只有对角线和对角线旁边一个位置有东西
所以我们在第 \(i\) 行只消掉第 \(i+1\) 行的第 \(i\) 个位置,这样最后剩下的就是一个右上矩阵了
复杂度 \(\mathcal O(n^2)\)
至于为什么不能消上一行,实际上在消的时候,会把上一行的一个 \(0\) 给搞成不是 \(0\),这个时候相当于还是一个右上矩阵

code

P3232 [HNOI2013]游走

显然可以想到一个贪心,让走过次数多的边的编号尽量的少
所以转化成了每个边的期望经过次数
因为边很大,所以发现可以转化成求点的经过次数
\(f_i\) 表示 \(i\) 的期望经过次数,\(du_i\) 表示 \(i\) 的度数
那么对于一条边 \((u,v)\),他的期望经过次数就是

\[\dfrac{f_u}{du_u}+\dfrac{f_v}{du_v} \]

所以接下来的问题就是求 \(f_u\)

显然 \(f_n=0\)
对于 \(i\neq 1\)

\[f_i=\sum \dfrac{f_j}{du_j} \]

对于 \(i=1\)

\[f_i=\sum \dfrac{f_j}{du_j}+1 \]

高斯消元即可
\(\mathcal O(n^3)\)
code

P4457 [BJOI2018]治疗之雨

式子比较好推,细节特别多,思路清奇的高斯消元题(

\(E_i\) 表示 \(i\) 期望多少次可以变成 \(0\)
显然 \(E_0=0\)
对于 \(i<n\),有

\[E_i=\sum_{j=1}^i (\dfrac{1}{m+1}p(i-j+1)+\dfrac{m}{m+1}p(i-j))E_j+E_{i+1}p(0) \]

对于 \(i=n\),有

\[E_i=\sum_{j=1}^i p(i-j)E_i \]

其中 \(p(i)\) 表示这个位置被减了 \(i\) 次的概率
那么根据高中数学知识

\[p(i)=\dbinom{k}{i}(\dfrac{1}{m+1})^i(\dfrac{m}{m+1})^{k-i} \]

发现 \(k\) 很大,但是我们把组合数展开成阶乘的形式,我们发现这个东西是可以递推的

\[p(i)=p(i-1)m^{-1}i^{-1}(k-i+1) \]

所以我们就可以确定每一项的系数,可以建立出消元的矩阵。
但是又有问题了,\(n=1500\),怎么高斯消元呢
观察发现,我们的右上矩阵只有 \((i,i+1)\) 这个位置上是有数的
所以我们处理第一行的时候,利用 \(g_{1,1}\)\(g_{1,2}\) 把下面每一行的第一列给消掉,复杂度 \(\mathcal O(n)\)
处理第二行的时候,发现这个时候只有 \(g_{2,2}\)\(g_{2,3}\) 是有数的,所以再把下面的每一行的第二列消掉
以此类推
就可以在 \(\mathcal O(n^2)\) 的时间里消出一个右上矩阵来了
复杂度 \(\mathcal O(Tn^2)\),时限给了 \(4s\),非常充足

注意一些细节的特判

  • \(k=0\)
  • \(m=0,k=1\)
  • \(m=0,k\neq 1\)

特别是最后一类,里面有可能出现 3 3 0 3 这种数据,实际上应该输出1
以及 -1/2=0

code

CF113D Museum

\(f(i,j)\) 表示一个在 \(i\),一个在 \(j\) 的概率
那么对于 \((i,j)\neq (a,b)\)

\[f(i,j)=\sum_x\sum_y p(i,j,x,y)f(x,y) \]

对于 \((i,j)=(a,b)\)

\[f(i,j)-1=\sum_x\sum_y p(i,j,x,y)f(x,y) \]

但是注意 \(x\neq y\)
高斯消元即可

\(\mathcal O(n^6)\)
code

CF432D Prefixes and Suffixes

显然答案就是 \(nxt[nxt[nxt[....nxt[n]]]]\) 这样的
接下来的问题转化成了,求一个前缀在串中的出现次数
考虑一个 dp,用 \(f[i]\) 表示 \(s[1...i]\) 出现的次数
那么初值是 \(f[i]=1\)
考虑一个倒推,对于一个 \(s[1...i]\),那么他的 \(nxt[i]\) 会额外出现一次
我们把 \(f[i]\) 累加到 \(f[nxt[i]]\)
这样就可以 \(\mathcal O(n)\) 解决了

code

CF576E Painting Edges

首先判断一个图是不是二分图,只需要看是否存在奇环
发现撤销操作不太好搞,所以线段树分治
但是我们在最开始的时候并不知道一条边的颜色应该是什么
但是没有关系,因为如果一个操作不执行,也等价于先把原来的边撤销掉,然后加上一个和之前一样的边
所以对于每一条边,我们还是可以对应在 \(\mathcal O(n\log n)\) 个节点上
然后我们需要在一条边插入完之前判断他的颜色
发现对于第 \(i\) 条插入的边,他在 \(i+1\) 之后的时刻才会有影响
而当递归到区间 \([i,i]\) 的时候,我们只要在这里确定他的颜色即可
利用一个带权并查集维护即可
因为需要撤销,所以不能路径压缩
总复杂度为 \(\mathcal O(n\log^2n)\)

code

P4287 [SHOI2011]双倍回文

猜出了结论没敢写
首先我们不能把 \(ww^Rww^R\) 当成是一个 \(ww^R\) 重复两遍
而要看成一个回文串套回文串
所以也就是说,我们在求 hw 的时候一定是要被包含的
那么我们自然联想到让 maxright 来包含
但是这样是不是会有问题呢,如果他被一个比 maxright 还小的串包含了
考虑这个这个答案,因为 mid-maxright 的串是回文的,所以这个答案一定在左边出现过
显然这个答案的 \(mid\prime\neq mid\)
所以这个时候他在左边的时候一定此时的 maxright 还不是后来的 maxright
这个时候会被统计到答案里面去
所以这个做法是没问题的
\(\mathcal O(n)\)

code

P7323 [WC2021] 括号路径

background

暴力怎么做?加边,加边,加边,并查集查询。

solution

其实这题想明白并不难。。。
考虑如果 \(x,y\) 可以,\(y,z\) 可以,那么 \(x,z\) 也可以
并且如果 \(x,y\) 可以,\(y,x\) 也可以
所以我们可以把能放在一起的合并到一个并查集集合里面
如果 \(x1,y1\) 有边,\(x2,y2\) 有边,\(y1,y2\) 在一个集合里
那么就可以合并 \(x1,x2\)
以此类推,我们用一个队列来模拟这个合并过程就可以了
因为需要合并边之间的信息,所以我们采用启发式合并的方式即可
复杂度为 \(\mathcal O(m\log m)\)

code

posted @ 2021-02-04 13:38  YuukiYumesaki  阅读(100)  评论(3编辑  收藏  举报