Loading

贡献延后计算

这是 DP 的一个 trick,挺难。

CF2066D

直接看 Hard Ver.

我们很快发现,已经 \(\ge c\) 的住户一定是一段前缀 \([1,j - 1]\),因此,我们的状态一定和「当前的纸飞机编号」、「已经满足的住户前缀」有关。于是,我们初步设置状态 \(f(i,j)\),代表考虑到第 \(i\) 个飞机,\([1,j - 1]\) 已经满足条件的情况。

但是,不难发现,在转移过程中,我们需要知道 \([j,n]\) 的所有看到的次数,这样的话,还要加的信息,维度其实是 \(\mathcal{O}(n)\) 的,这很不好。因此,我们尝试用组合数来优化转移。

这时,我们便引出贡献延后了。具体地,我们将过程划分为 \(n\) 层:

  • \([1,n]\) 总共扔下 \(c\) 个飞机。

  • 上一阶段结束后,到 \([2,n]\) 总共扔下 \(c\) 个飞机。

  • ......

  • 上一阶段结束后,到 \([n,n]\) 总共扔下 \(c\) 个飞机。

在每一层里面,我们不考虑具体选择 \([j,n]\) 里的哪一户投飞机,只不过在约束下随便投一架。我们的任务,是把从上一层转移过来的所有贡献,收集到一起。

当这一个阶段结束的时候,我们就要计算贡献,并且把这个节点转移到下一层了。这个贡献可以用组合数计算。

因此,我们设计状态 \(f(i,j,k)\) 代表,考虑了前 \(i\) 个纸飞机,目前正在阶段 \(j\)\([j,n]\) 已经投下 \(k\) 架纸飞机的情况。它记录的是:目前尚未考虑第 \(j\) 层的系数,这一条件下的情况数。

那么,层内的转移很简单。考虑如果 \(a_{i+1} \ge j\) 的话,就说明,\(a_{i+1}\) 可以作为 \([j,n]\) 里的一个纸飞机投放,我们直接:\(f(i,j,k) \to f(i + 1, j, k + 1)\) 即可。否则不转移,因为不合法,那么 \(f(i,j,k)\) 里的方案全部废掉。

跨层的转移,只会出现在 \(f(i,j,c)\)。我们发现,由于上述的限制,\(a\) 的这 \(c\) 个位置只会包含 \(0\) 或者 \(\ge j\) 的数。根据前面的转移,在 \(a[1,i]\) 中,\(\ge j\) 的数一定全部包含在 \(c\) 中的,那么,只会有 \(c - cnt_{a[1,i]\ge j}\)\(0\),我们可以自由的在这些 \(0\) 之间决定哪些是 \(j\),剩下的 \(> j\)。我们枚举所有 \(cnt_{a[1,i]=j} = A\),转移就是 \(f(i, j, c)\binom{c - cnt_{a[1,i]\ge j}}{A - cnt[1,i] = j} \to f(i, j + 1, c - A)\)

基本思想应该是讲明白了,考虑一下这样为啥是对的。首先,这种转移一定不会漏。层内的转移肯定没有问题,我们考虑证明某一层到下一层的转移中,不会算出重复的情况,这时只有 \((i,k)\) 是对证明有影响的:

  • 对于同一个 \((i,k)\) 内我们对 \(A\) 的枚举,既然序列中 \(j\) 出现的次数都不一样,两个序列肯定不同。

  • 对于不同的 \(i\),这意味着最后一个 \(\ge j\) 的数出现的时间不同,转移到下一层的两组序列肯定不交。

  • 对于不同的 \(k\),这意味着序列里出现 \(\ge j\) 的数的次数不一样,转移到下一层的两组序列肯定不交。

从一个方案对应一条转移路径的角度来想,这就是对的。

posted @ 2026-07-30 14:15  Trent900  阅读(14)  评论(0)    收藏  举报