CF1875E-DP状态设计

论 DP 状态设计:

问题描述:

给定一个 \(01\) 串,每次可以交换相邻的 \(0\) 和 \(1\) 。问 \(k\) 次操作过后所有可能的状态有多少。

\(n\le1500 ,k\le1500\)

一. 简单转化

对于这种多种操作方案却可能对应同一个结果的问题,肯定要去考虑某一种方案合不合法。

那么什么样的最终序列是合法的呢?

显然,由于相邻两个 \(1\) 的相对位置无法发生变化,设某个 \(1\) 操作完后相对于原来的位移为 \(dis_i\) ,所以合法需要满足:

  • \(\sum_{}dis_i\le k\)

  • \(\sum dis_i≡k (mod\ 2)\)

那么就是显而易见的 \(dp\) 。且位移和肯定要算到一个状态里。

二. 论两种 DP 方式:

1. dp[第i个1][目标位置j][花费t]

这样可以很容易地写出转移,然后前缀和优化至 \(O(n^3)\) ,极限卡常可以过。

2. dp[扫描位置i][前缀差d][花费s]

前缀差 \(d\) 表示当前前缀中实际的 \(1\) 的个数与给定 \(01\) 串中的个数的差。

假设现在扫描到第 \(i\) 个位置,初始串这一位的值是 \(a_i\) (0 或 1)。
我们枚举目标串这一位 \(x\)(\(x = 0\) 或 \(x = 1\))。

  1. 计算新前缀差:

\[nd = d + x - a_i \]

(解释:新前缀差 = 旧前缀差 + 目标值 - 初始值)

  1. 计算新累计代价:

\[ns = s + |nd| \]

(为什么加 \(|nd|\)?因为 \(|nd|\) 就是跨越当前边界 \(i\) 和 \(i + 1\) 的 '1' 的数量,每跨越一个 '1' 都要消耗一次交换操作)

  1. 转移方程:

\[dp_{\text{new}}[nd][ns] += dp_{\text{old}}[d][s] \]

这一步也是非常的妙哉,然后这样不加前缀和优化都能实现 \(O(n^3)\)

  1. 关键发现:

\(d\) 的范围是 \([-\sqrt{k},\sqrt{k}]\) 。

这是因为有 \(d\) 个 \(1\) 需要跨过中线,而这样的代价至少是 \(1+2+...+d\) 那么由于总代价不超过 \(k\) ,所以 \(d\in[-\sqrt{k},\sqrt{k}]\) 。

三. 差别:

关注移动与关注有无,与其关注某一个 \(1\) 最终移动到了那里,不如去关注最终某个位置有没有 \(1\) 。

当然,这两种思考方式都很重要。但本题后者显然更优。

posted @ 2026-10-02 15:21  wlhao  阅读(6)  评论(0)    收藏  举报