Solution Set - NOI 2022

游记

众数

Solution

有个题叫「POI2014」Couriers,这个题启示我们实际上问题等价于询问哪个数出现次数最多,最后只需要判断是否超过一半即可。

考虑线段树,对于一个序列,开一棵线段树维护上面的权值,单个序列的查询可以在线段树上面走,选择大的那一边走下去,最后的就是答案,多个序列可以考虑 \(m\) 个线段树一起走,同样选择大的一边,注意到 \(\sum m\le 5\times 10^5\),所以这样做复杂度是对的。

查询解决了,接下来考虑修改,修改在线段树上的影响不过是单点改和合并,这些都是简单的,但是注意到我们要维护每个序列的末尾,以及合并两个序列,可以使用启发式合并做到 \(O(n\log n)\)

更优的想法是,可以使用链表的形式来维护,因为我们只需要知道末尾元素,而合并操作可以直接把一个链表的尾部元素的 next 指针指向另一个链表的开头元素,从而做到线性。

总时间复杂度线性对数,但是据说有高论可以做到线性,我还不会。

赛时我实现了这个做法,但是因为不明原因的链表写挂,所以只有 \(75\) 分。

Code

代码使用 std::list 来实现启发式合并。

Link

移除石子

Solution

你先考虑怎么判无解,考试的时候我就完全不会判无解。

暂时先不考虑加石子,设计 DP 为,\(f_{i,j,k}\) 表示,目前在第 \(i\) 个位置,有 \(j\) 个操作二要延伸到 \(i\),且有 \(k\) 个一定要延伸到 \(i+1\) 或者更远,这个 DP 是容易转移的,只需要枚举新开的二操作 \(l\) 和接下来要延伸到下一个位置的二操作数 \(p\in[k,k+j]\),则 \(f_{i,j,k}\) 可以转移到 \(f_{i+1,p,l}\)

感觉上 \(j,k\) 需要开很大,但是注意到我们其实只需要找到一组合法操作,考虑压缩状态,是不是 \(j,k\) 可以开的稍微小那么一点?

首先先规范一下我们的操作,为了使 DP 状态更少,我们考虑强制如下规则:

  • 优先一操作。
  • 然后,优先短的二操作。

那么首先你有几个发现,一个是相同的区间一定不会做两次二操作,因为会被转成多个一操作,还有长度 \(\ge 6\) 的二操作没啥意义,因为可以拆分。

意识到这点之后,二操作有用的就只有长度为 \(3,4,5\) 的,然后你会发现他们同时存在也没有任何意义,因为 \([i,i+2],[i,i+3],[i,i+4]\) 可以变成 \([i,i+2],[i+1,i+3],[i+1,i+4]\) 和一次对 \(i\) 的操作 \(1\)

故,\(j\le 4\)\(k\le 2\) 是可行的。

这个时候你再理性分析一下,能不能再继续缩减,手玩会发现,\(j=3\) 的所有情况都可以被调整成 \(j\le 2\) 的情况,于是 \(j\le 2,k\le 2\) 是可行的。

据称有人减一维强行减出来了,十分膜拜。

接下来考虑加石子,一个感觉是如果说在加了若干石子使局面合法的情况下,再加上一枚其实不会有太大影响,除了某些特殊情况,讨论一下:

  • \(k=1\) 时:

    • 如存在操作一,则一定有解,因为可以让操作一多减一个。
    • 如存在操作二长度 \(>3\),则一定有解,你可以把某个操作二长度减个一然后添加一个操作一。
    • 如存在操作二长度为 \(3\)\(n>3\),则一定有解,你可以把操作二长度延长 \(1\)
      然后你会发现这种情况下无解的情况,要么全为 \(0\),要么 \(n=3\) 且全为 \(1\),恰恰这两种情况 \(k=0\) 都是有解的。
  • \(k\ge 2\) 时:

    • 考察 \(k=1\) 时无解的两种情况,考虑上述中添加石子会爆炸的两种情况,你会发现全为 \(0\) 根本不可能加出来,全为 \(1\) 去掉一个石子后是无解的。
    • 其余情况你可以随便选个位置加两个石子。

特判掉 \(k=1\) 的两种情况,我们的新结论是,如果添加 \(k-1\) 颗石子有解,则添加 \(k\) 颗石子有解。

于是乎,修改状态为最少需要加的石子个数,考虑个数的计算,设 \(v=a_i-j-k-l\),若 \(v<0\),则需要 \(-v\) 颗石子,若 \(v=1\),则需要一颗石子,其余情况不需要石子。

类似 DP 即可,最后判断 \(f_{n+1,0,0}\le k\)

经历了如此复杂的讨论,恭喜你拿到了 \(40\) 分。

接下来考虑如何计数,一个感觉是,\(r_i\) 太大没啥意义,换句话说,一个数选 \(v\) 和选比 \(v\) 小的 \(v'\) 本质上是相同的,结合上面的状态分析,实际上我们只需要取 \([1,6]\) 之中的数就行了,这个是十分显然的。

这个时候就是经验了,你发现外层计数好像要 DP,内层判定好像还要 DP,DP 套 DP 硬冲就完事了!!!!1

理论上这个 DP 套 DP 有 \(100^6\) 种状态,但是你爆搜一下发现只有 \(8000\) 多种,所以硬冲就过了。

Solution

Link

挑战 NPC II

Solution

提示里面写的很好,本题需要使用树哈希,考虑到树哈希的本质是将一个子树映射成一个数值,也就是可以方便地判断两个子树是否相等,由此我们考虑一个构造过程:

首先使用树哈希求出两棵树(下文分记为 \(T_1,T_2\))每一棵子树的哈希值,考虑如何匹配两棵树的子树,一个显然的事情是,我们希望被匹配的两棵子树得是同构的,那么我们先将这些同构的找出,剩下的子树存起来。

注意到 \(T_1\) 剩余的子树个数一定需要大于等于 \(T_2\) 剩余的子树个数,同时 \(T_1\) 剩余的子树个数必须 \(\le k\),否则无解。

考虑将 \(T_1\)\(T_2\) 剩余的子树进行匹配并重复递归执行上述过程,我们希望求出某一个匹配,其中删点数量恰为某一个值,但是实际上我们并不能快速计算这个东西,所以只能暴力枚举 \(k!\) 种匹配递归。

由此,我们得到了一个复杂度十分玄学的算法,上界是 \(O(\prod_{i=1}^ki!n)\),但是似乎跑不满?可以通过。(UPD:有证明是 \(O(2^kn)\))。

笔者没有学过树哈希,所以考场上编造了一个树哈希算法,其表现不错,可以拿到 \(92\) 分,这里就不说了,下面是几个可能可以通过的树哈希算法(笔者也没有具体实现):

  • 钦定一个 \(B\),满足 \(B>>h_i\),在求 \(h_u\) 的过程中排序儿子的 \(h\),从小到大执行一个类似字符串哈希的过程,即求出 \(\sum h_v\times B^i\),然后加上 \(u\) 的信息。
  • 将树转成括号序列后,对于括号序列进行字符串哈希。
  • 膜拜 dls:https://peehs-moorhsum.blog.uoj.ac/blog/7891
Code

Link

冒泡排序

Solution

下面没有证明。

首先使用区间覆盖或者并查集判断无解,下文假设其有解。

判断无解的时候,可以顺带求出每一个值的最小值。

结论一:对于每一个区间,我们的最小值一定丢在尽可能左。预先丢上这些最小值。

结论二:从右往左填,局部最优解为全局最优解。

考虑如何维护每一个数的答案,首先对于每一个数的最小值 \(low_i\),预先为 \([1,low_i-1]\) 添加贡献 \(1\),对于一个被扫到的 \(j\)

  • 如果其有 \(low_j\)\([1,low_j-1]\) 减去 \(1\),因为贡献转换了。
  • 如果其没有规定值,求出 \([low_j,m]\) 的最小值,这就是当前点的答案。
  • \([val_j+1,m]\) 添加 \(1\)

最后对于 \(val_i\) 求逆序对个数即可,时间复杂度 \(O(n\log n)\)

Code

Link

二次整数规划问题

Solution

大概复读一下官方题解(?)

首先先处理变量之间的约束,这是一个差分约束问题,解决这个问题的复杂度不是瓶颈,这里不讨论具体细节。

那么现在,我们的问题就变成了,有若干个数,每个数有其取值区间,满足目标函数最大。

结论一:如果有取值区间可以取非 \(1\)\(k\) 的数,那么其不取 \(1\)\(k\) 不劣。

证明:注意到一个细节,\(1\)\(k\) 是没有贡献的。那么将 \(1\)\(k\) 改成其他数只会损失掉其作为 \((1,2)\)\((1,1)\) 贡献 \(G\) 的情况,但是改成 \(2\) 这两者贡献又会被加回来,\(k\) 的情况类似。

由此我们可以导出 \(k=3\) 的做法,也就是将能取 \(2\) 的取 \(2\),然后对于 \(v\) 计算答案。

同时也注意到序列这个东西其实是个幌子,我们只需要计算每个数出现了多少次,下文记 \(c_i\) 表示 \(i\) 出现的次数。

考虑将其扩展到 \(k=4\),应用结论一之后,我们去除掉一些必须选 \(2,3\) 的元素,剩下的元素可以选 \(2\) 也可以选 \(3\),假设这些元素共有 \(n\) 个,选了 \(x\)\(2\),那么增加的贡献是 \(10^6(c_1+c_2+c_3)x+v_2x+10^6(c_2+c_3+c_4)(n-x)+v_3x\),这是一个关于 \(x\) 的一次函数,故最值在 \(x\) 的最值处取到,所以只有两种情况,取最大值即可。

最后考虑 \(k=5\),根据上面的思想,我们将 \((c_2,c_4)\) 作为每一个序列的代表,考虑相比于 \((0,0)\) 时序列贡献的变化,为:

\[c_2(v_2-v_3+2\times 10^6c_1)+c_4(v_4-v_3+2\times 10^6c_5)-2\times 10^6c_2c_4 \]

存在 \(c_2,c_4,c_2c_4\) 的项,故可以划归成 \(-V(c_2-a)(c_4-b)+C\) 的形式,问题也就变成了要求最小化 \((c_2-a)(c_4-b)\)

问题等价于平移 \((c_2,c_4)\)\(c_2c_4\) 最小,对 \((c_2,c_4)\) 平移后的分布做分类讨论:

  1. 全都在第一象限,那么最优情况为能选 \(3\) 的选 \(3\),也就是对应 \((\min c_2,\min c_4)\)

  2. 部分在第三象限,那么最优情况一定在二四象限,对应 \((\max c_2,\min c_4)\)\((\min c_2,\max c_4)\)

  3. 全在第三象限,那么此时答案一定在右上凸包。

我们只需要找到右上凸包,就可以用凸包点数的代价求解一组询问。

考虑如何求凸包,我们使用类似最小乘积生成树的方法,对于凸包上相邻的两点 \((x_1,y_1),(x_2,y_2)\),满足 \(x_1<x_2\),我们求出与 \((x_1-x_1,y_1-y_2)\) 叉积最大的点 \((x',y')\),仿照最小乘积生成树,左右递归下去。

每一次分治过程中我们希望解决如下问题:选 \(2\)\(y_1-y_2\) 的收益,选 \(4\)\(x_2-x_1\) 的收益,满足条件的情况下收益最大。

更换一下问题描述,先加上 \(y_1-y_2+x_2-x_1\) 的收益在算代价,所以就变成了选 \(2\)\(x_2-x_1\) 的代价,选 \(3\)\(y_1-y_2+x_2-x_1\) 的代价,选 \(4\)\(y_1-x_2\) 的代价,同时变量之间有限制,这是一个切糕型网络流。

时间复杂度为 \(O(n^{2/3}(\text{Dinic}()+q))\)

Code

Link

posted @ 2022-10-08 22:16  时一月  阅读(230)  评论(0)    收藏  举报