【题解】7月11到7月17典例复习
因为网盘还没有搭建好,题面 PDF 无法上传,所以暂时没有题面
7 月 17 日#
1. maruyu#
是一道让我非常头疼的数论题面,刚刚好让我好好复习一下。
回顾一下题面,要用n+1艘船运送k种物质,而且要满足一下两个条件:
- 没有任何一种物质同时被0∼n−1艘船运输;
- 第1∼n艘船没有运输全部k种物资;
现在问你:一共有多少种运输方案?(对 1e9 + 7 取模)
那么看起来很难(确实很难),我们用一个矩阵来理解。
假设每一行从左往右从 0 开始,到n结束;每一列由上而下从 1 开始,从k结束。
我们规定 0 代表第i艘船选择了第j种物质;1 就代表选择。那么我们可以用(i, j)来表示一个状态,从这里开始分析;
- 首先明确题面,我们不需要运输完所有的k种物资,那么我们从 1 到k枚举一个实数i,代表有i种物质没有被取走;
- 那么我们很简单可以知道这样一共有Cki×2i种情况
- 接下来我们开始考虑其他的取走的物品的情况
- 因为头尾的两艘船十分特殊,我们先不考虑它们,先考虑1∼n−1;
- 每一条船可以取也可以不取,一共是2n−1种情况
- 因为我们现在不知道头尾的情况,我们先去掉都是 1 和都是 0 的情况,也就是-2
- 我们再考虑前后两个特殊的值,把刚才的式子再乘以22;
- 最后我们要把刚刚去掉的全部都是 1 和 0 的情况补回来,有下面四种合法情况
- 0 111111111111…111
- 0 111111111111…110
- 1 000000000000…001
- 1 000000000000…000
- 那么此时我们单个物资的情况就是(2n−1−2)×4+4;
- 最后此时我们有k−i种物质被取走了,那么最后的结果是((2n−1−2)×4+4)k−i
- 现在我们可以得到一个式子∑i=1kCki×2i×((2n−1−2)×4+4)k−i;
- 但是这样我们处理非常麻烦,需要化简(使用二项式定理):
- 二项式定理:(a+b)k=∑i=0kCki×ai×bk−i
- 我们把(2n−1−2)×4+4用一个变量R代替,重写得到 ∑i=1kCki×2i×rk−i;
- 容易发现∑i=0kCki×2i×rk−i=(r+2)k,但是这里的i是从 0 开始的,我们还需要减去;
- 最终化简结果为∑i=1kCki×2i×((2n−1−2)×4+4)k−i=∑i=1kCki×2i×rk−i=(2+r)k−rk
- 最后使用快速幂计算输出。
注意最后有减法,不要忘记加上 mod 之后再减避免出现负数
FIX 代码#
2. truck#
我本来的想法是二分答案验证联通,但是发现复杂度为O(Q∗logN(N+M)),得分 60Pt。
思路来自 HYX 大佬,而非 std
主要思想:最大生成树
&并查集
首先我们使用pair
离线存贮所有的提问,因为是无向图,我们要双向存储。其中 first 代表的是目标节点,second 代表的是提问编号。
我们把所有的边放在一个vector
中,然后定义以边权从大到小排序,用来做最大生成树;
为什么这样做?#
翻译一下题面,就是我们要找两点之间简单路径上面最大的最小值! 可能有点拗口,就是在保证联通的情况下,我们这一条路径上面的最小值尽可能大 那么我们就可以用最大生成树,把边从大到小依次添加,直到 uv 在同一个连通块里面 那么此时添加的这一条边就是 uv 两点之间简单路径上面最大的最小值,也就是答案 那么就很简单了,我们从大到小添加边,然后合并,直到 uv 合并,记录 ans 那么我们只需要遍历提问,然后操作并查集,通过路径压缩
和启发式合并
完全可以通过题目
算法实现#
联系我们之前强连通分量的内容,其实我们这里并查集做的事情差不多,就是把几个点合并成一个点。那么我们先定义一个数组fa
代表这个点从属于哪个大点。刚开始fa
是他们自己,每次把小的合并到大的,更新fa
。
这里我们写一个函数来获得他从属于哪一个大点。
这里我们fa[a] = get_fa(fa[a])
是路径压缩,把父亲直接指向根,节省时间。
最后我们写合并函数。前面我们说了用pair
来存储询问,那么其实每一次合并时也合并了询问,我们也要把询问放入并查集->我说过这就和合并几个点相同,其实刚开始每一个点都是一个大点,只不过他们只包含自己而已。
分析查询询问的过程,如果我们查询到的询问的目标在同一个大点中,说明只要连接这一条边,他们就联通了,此时我们把这一次询问的答案记录为此时的边权;如果不再一个大点中,我们就把它合并到一起,进入下一次合并的过程。
最后我们把这个大点归属到需要合并的点中,那么下一次get_fa
时,里面所有小点的fa
都会更新。
注意我们要保证时间复杂度在 log 内,需要严格把小的合并到大的,也就是所谓的启发式合并。
7 月 16 日#
1. xor#
因为题面中 b 是严格递增的,那么我们可以不走回头路,在O(N2)解决问题。
总的来说就是先存储前面两个元素 xor 的值有几个,然后在后面匹配。
代码实现#
首先定义cntk[x]的含义是当第三个元素在k位置时
前两个元素 xor 的值为 x 的匹配个数。
我们先定一个指针k来指代第三个元素的位置。那么每次移动指针k时可以通过遍历a[i] xor a[k]
,就可以直接处理好我们需要的cntk+1,这段操作不计时间复杂度。
然后我们从k + 1
开始遍历第四个元素l
的位置,每次ans += cnt[a[k] ^ a[l]]
,也就是加上当前能够匹配上的元素。
遍历 k 和 l,时间复杂度为O(N2),可以解决问题。
2. multiplication#
反正我们不知道谁是谁,我们直接假设元素从小到大排列整齐,方便分析。
那么排好之后是一个[0,p−1]×[0,p−1]的一个p×p矩阵,很像我们的 99 乘法表。
那么用我们聪明的小脑袋想一想,如果是n×p(n<p),那么很显然十位就是n。但是我们最大为(p−1)×(p−1),简单拆开就是p2−2p+1,刚刚好得到的数字为[p−2][1];
根据这个规律我们可以写出这样一个对应表:
|我们从 0 到 p-1 的数字| p-1 | | :title: 【题解】7 月 11 到 7 月 17 典例复习 tags:
-----: | :-----------: | | 0 | [0][0] | | 1 | [0][p-1] | | 2 | [1][p-2] | | 3 | [2][p-3] | | … | … | | p-1 | [p-2][1] |
容易发现,一个正整数数在 p 进制下乘以 p-1,它的十位刚刚好就是它自己减一。
但是回过来,我们这个矩阵既不是有规律的,甚至于我们都不知道数字是几,但是我们可以通过其他方法记录这个值。
我们知道一个数最大时的十位是它自己减一,这说明什么?说明在之前的十位它都曾出现过。假设有一个数字三,乘以 p-1 得到一个数字十位为 2,这说明它在这个矩阵中一定有十位为 0,1,2 的数字。因为即使 p 最小最小为 3 时,每一次相乘也就是在之前的基础上加三,十位同时进一位,不可能有进两位的情况。
那么就简单了,我们只需要统计一个数在这个矩阵的一行中的数字的十位所出现的种类数,就是它。
0 需要特判,因为 0 乘以任何数都是 0
3. four#
这个题目很复杂,很烦躁,要静……
但是理解了之后,也非常的简单……
首先方案达到了240级别,基本上属于摆烂的节奏,我们把它先分成两半再匹配,分开来是为了防止暴力爆炸,分成两份是保证脑子不爆炸。
我们先把分开来的 20 个元素变成220个元素,可以直接这样写:
然后容我来讲解一下判断方法:
- 我们先在前后各220中选出两个元素
- 通过
pw
取模拿出这两个元素的一段 - 然后相加这两项,那么我们可以简单判断是否在
pw + 1
位存在 4- 如果相加的值在[4 * 10^pw, 4.999… * 10^pw]或者[14 * 10^pw, 14.999… * 10^pw]
- 就说明在这种情况之下的
pw + 1
为 4
就这么简单……我也想不到还有什么好说的了,自行阅读代码理解吧。
7 月 15 日#