专栏文章

杂题笔记

个人记录参与者 1已保存评论 0

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
0 条
当前快照
1 份
快照标识符
@minndl0a
此快照首次捕获于
2025/12/02 05:14
3 个月前
此快照最后确认于
2025/12/02 05:14
3 个月前
查看原文

Ice Baby

  • 首先需要掌握最长不降子序列的二分做法:定义 fif_i 表示:长度为 ii,末尾最小值。
  • 研究一下会发现,每次我们需要进行一个平移操作:设题目给的区间为 [L,R][L, R],设 ll 为最后一个满足 fiLf_i \le Liirr 为最后一个满足 fiRf_i \le Rii。要把 f[l+1,r]f_{[l+1, r]} 平移到 f[l+2,r+1]f_{[l+2, r+1]},并把 flf_l 设置为 LL
  • 注意到:一,f 数组是满足单调不减的;二,我们需要维护的只是最大的长度(即 f 数组有值的范围)。因此,维护 f 数组的下标变得不重要了。 可以用 multiset 来代替 f 数组,储存数组内的值即可。
  • 这么做的好处是,平移操作只需要考虑对 llrr 附近的影响即可,不需要处理下标变化。恰好,multiset 也是支持 lower/upper_bound 操作的。
  • 结合提交代码。
  • 经验总结:平移操作,要看看下标是否重要,如果不重要,想想可不可以用 multiset 代替。

We're teapots

  • 区间 [l,r][l, r] 内(设长度为 rl+1=nr - l + 1 = n)取 cc 个点,且不能相邻,方法数:Cnc+1cC_{n - c + 1}^c。证明:要取 cc 个点,就有 ncn - c 个空位,现在要在这些空位之间插入 cc 个选择的点(插板法),且不能插在同样的间隙中,因此方案数为 Cnc+1cC_{n - c + 1}^c
  • 矩阵乘法:n×mn \times m 矩阵 A 乘以 m×km \times k 矩阵 B 得到 n×kn \times k 矩阵 C:Ci,jC_{i,j} 就是 A 的第 i 行和 B 的第 j 列都是 m 个数,让它们一对一乘起来再加起来。

Triangle Toggle

  • 性质题。对于这类性质题,不妨考虑每次操作中某些量的变化
  • 这道题,定义与某个点相连的黑边数为 f(u)f(u)。每次操作可以使得选中的三个点的 f 分别增加 0,0,2 或者 0,0,-2 或者 2,2,2 或者 -2,-2,-2。故每个点的 f 可以在奇偶性不变的条件下任意变化。
  • 这个证明不是特别严谨,因为增加的值是原本三条边的颜色决定的。可以这么想:如果需要给(且可以给)某个点 u 的 f 加 2,那么它就必定连着两条白边 (u,x)(u, x)(u,y)(u, y)。如果 (x,y)(x, y) 是黑的,那么就是给 f(u)f(u) 加 2,其他不变;如果 (x,y)(x, y) 是白的,那么就给 f(u)f(u)f(x)f(x)f(y)f(y) 都加 2,这样更好。总之就是能把所有点的 f 都尽可能不断加 2。

Sliding Tree

  • 答案:n - 树的直径。
  • 每次操作(最优情况下)可以使树的直径加一。把直径作为主链,只要每次选择直径上一个有分支的点 u,假设分支的第一个点为 v,那么把直径的某一半从 u 接到 v 上即可。
  • 这同样是观察操作过程中某个量的变化。

Non-Ancestor Matching

  • 树形 dp。
  • 每一步,我们遇到的是这样的局面:对于点 u,它有 k 个子树,这些子树中不能内部消化的点的数量分别为 a1,a2,...,aka_1, a_2, ..., a_k,现在要尽可能地进行配对,并处理出以 u 为根的子树中不能内部消化的点的数量。
  • 如果 a 中最大的数大于其他所有数之和,那么总的不能内部消化的点的数量就是(a 中最大的数 - 其他所有数之和)。否则,可以证明一定可以配对使得落单的点的数量是 0 或 1(依奇偶而定)。最后还要加上 u 这个点。

Subset Product Problem

  • 折半查找。
  • 枚举超集(其中 f[st] 表示对于 st 的所有子集 x,【x 的【出现次数】次方】之积): CPP
    for i in [1, n]:
      for st in [0, 1 << 20):
        if a[i] | st == st:
          f[st] *= a[i]
    ans[i] = f[a[i]]
    
  • 枚举子集(其中 g[st] 表示 st 的【出现次数】次方): CPP
    for i in [1, n]:
      for st in [0, 1 << 20):
        if st | a[i] == a[i]:
          ans[i] *= g[st]
    g[a[i]] *= a[i]
    
  • 折半(其中 F[x][y] 表示对于所有【前十位包含于 x,后十位等于 y】的二进制数,【它们的【出现次数】次方】之积): CPP
    for i in [1, n]:
      for st in [0, 1 << 10):
        if a[i].前十位 | st == st:
          F[st][a[i].后十位] *= a[i]
    
      for st in [0, 1 << 10):
        if st | a[i].后十位 == a[i].后十位:
          ans[i] *= F[a[i].前十位][st]  
    
  • 枚举超集是修改 O(220)O(2^{20}),求值 O(1)O(1);枚举子集是修改 O(1)O(1),求值 O(220)O(2^{20});折半查找是修改 O(210)O(2^{10}),求值 O(210)O(2^{10}),就匀了。总的复杂度就是 O(n×210)O(n \times 2^{10})

Antiamuny Wants to Learn Swap

  • 从逆序对角度考虑。交换邻项可以让逆序对数量减一。“完美”的意思就是,交换“邻邻项”也只能让逆序对数量减一。
  • 实际就是:询问区间内是否存在递减三元组。
  • 用单调栈预处理出【前面最近的更大的数】和【后面最近的更小的数】,然后对于每个 r 处理出使得 [l, r] 内不存在递减三元组的最大的 l。这样询问就是 O(1)O(1) 了。

Maple and Tree Beauty (Hard Version)

  • 概述:把树分成一层一层,做优化的背包。
  • 下面称 0 为黑点,1 为白点。
  • 注意到最优的方案一定是使叶子节点的最长前缀子序列最长。因为如果不是前缀,下面的层节点数量会更多,就不好完成任务,不优。
  • 定义 f[i][j]:前 i 层(从上往下),每一层的点都只能染同种颜色,共染了 j 个黑点,可不可行?假设前 i 层共有 S[i] 个点,那么我们要判断,是否存在一个 j,使得 f[i][j] 为真,且 S[i] - i < N - K(白点数也不能超出限制)。如果不存在,就代表不可行,输出 i - 1。
  • 问题是,极端情况下树是一条链,这样层数(物品数)就是 n。这样背包就是 O(n2)O(n^2) 的。
  • 一种方法是用滚动数组 + bitset 优化。由于是可达性 dp,所以可以把 f[i][] 用 bitset 表示出来。时间复杂度 O(n2/W)O(n^2/W),其中 W 为计算机的位数(64)。
  • 另一种方法是:容量优化不了,考虑优化物品数。考虑多重背包优化。每一层的节点数是递增的。把节点数相同的层合并为一个物品。
  • 这样,每一个物品的重量(节点数)是递增的。一个严格递增数列和为 n,那么数列长度是 O(n)O(\sqrt n) 的。因此,物品数是 O(n)O(\sqrt n) 的。
  • 跑单调队列优化的多重背包,复杂度 O(nn)O(n \sqrt n)(虽然我不会 qwq)。

A Cruel Segment's Thesis

  • 反悔贪心。每个线段产生 R 或 -L 的贡献。假设所有的线段一开始都产生 R 的贡献,现在要选出一些线段,把它们的贡献从 R 改成 -L,也就是使总答案减少 L+R。把所有线段按照 L+R 排序,选择 L+R 最小的 n/2 个数,把它们的贡献从 R 改成 -L。

Price Tags

  • 设价格的值域为 V,f(v) 表示 v 这个价格的出现次数。以下是伪代码,遍历边界不一定准确。
CPP
for x in [2, V]: // 折扣倍数
  cnt = 0 // 有多少个牌子能重复使用
  for v in [1, V / x]: // 折扣后的价格
    get l, r // 要使得折扣后的价格为 v,原价的范围
    cnt += min(f(v), f(l ~ r))
  get tmp_ans // 折扣倍数为 x 时的收益
  ans = min(ans, tmp_ans)
output ans
  • V/1+V/2+V/3+...+V/VV/1 + V/2 + V/3 + ... + V/V 是调和级数,复杂度 O(VlogV)O(V \log V)

Almost Sorted 2

  • 对于某个数组重排的方案数问题:考虑从小到大(或按一定顺序)插入数字。
  • 求组合数:预处理出阶乘及其逆元。
    CPP
    fac[0] = 1;
    cfor (i, 1, n)
      fac[i] = (fac[i - 1] * i) % mod;
    inv_fac[n] = inv(fac[n]);
    bfor (i, n - 1, 0)
      inv_fac[i] = (inv_fac[i + 1] * (i + 1)) % mod;
    

评论

0 条评论,欢迎与作者交流。

正在加载评论...