QOJ 10499 [PA 2016 Final A] Dopasowanie

自己做出来了个单 \(k\) 的做法😎,有点厉害,记一下。

首先有一个最朴素的 dp,即记 \(f(i, j)\) 表示 \(s\) 的子串匹配到 \(i\)\(t\) 匹配了 \([1, j]\) 的最小花费,那么转移直接分讨:

  • 删除 \(t_{j + 1}\)\(f(i, j) + 1 \to f(i, j + 1)\)
  • 插入 \(s_{i + 1}\)\(f(i, j) + 1\to f(i + 1, j)\)
  • 匹配 \(s_{i + 1}, t_{j + 1}\)\(f(i, j) + [s_{i + 1} \not = t_{j + 1}] \to f(i + 1, j + 1)\)

那么若存在 \(f(i, |t|) \le k\),就说明可以以 \(r\) 为右端点,不过左端点并不清楚。

不过问题并不大,考虑让 \(\operatorname{rev}(s_{1\sim r})\)\(\operatorname{rev}(t)\) 再做一次上述的 dp,并强制 \((0, 0)\) 为起点即可(上述做法起点可以为任意 \((i, 0)\))。

于是这就得到了一个 \(\mathcal{O}(|s||t|)\) 的做法。

不过因为 \(k\) 很小,所以只有 \(f(i, j)\le k\) 的状态才是有用的。
于是有一个想法就是把状态设计成 \(g(i, k')\) 表示 \(f(i, j)\le k'\)\(j\) 的集合,但是会发现 \(f(i, j)\) 没有单调性,就只能 bitset 做了。

此时考虑从一个 01bfs 的思想来找到结构。
具体来说,考虑根据 \(k'\) 来扩张,首先扩张出 \(f(i, j)\le k' = 0\)\((i, j)\),然后不断尝试 \(k'\gets k' + 1\),观察扩张 \(f(i, j)\) 的过程。

首先发现对于 \(k' = 0\),因为只有斜向边权可能为 \(0\),此时扩张出来的一定满足对于每个 \(0\le i\le |s|\)\((i + x, j + x)\) 的一段前缀,也就是一个斜线。
然后发现 \(k'\gets k' + 1\) 后,会使每一段斜线往上平移 \(1\) 单位,往右平移 \(1\) 单位,然后向右上平移 \(1\) 个单位,平移后每一个 \((x, y)\) 会尝试向 \((x + 1, y + 1)\) 扩张(当斜向边权为 \(0\) 时)。

于是会发现,对于任意 \(k'\),考虑对于 \(i\in [0, |s|]\),满足 \(f(i + x, x)\le k'\)\(x\) 始终是一个前缀。不过这个时候还有 \(f(i, j)(i > j)\) 的情况,此时考虑把 \(i\) 扩展到负半轴,令 \(f(i + x, x) = x(i + x \le 0)\) 也满足以上条件。

于是考虑设 \(g(i, k')\) 表示满足 \(f(i + x, x)\le k'\) 最大的 \(x\)
转移就是 \(g(i, k') \gets \max(g(i - 1, k' - 1), g(i, k' - 1) + 1, g(i + 1, k' - 1) + 1)\),然后若 \(s_{i + g(i, k') + 1} = t_{g(i, k') + 1}\) 就继续扩张。

只需要快速做扩张的操作,会发现这就相当于是求 \(\operatorname{lcp}(s_{i + g(i, k') + 1\sim |s|}, t_{g(i, k') + 1\sim |t|})\)

用 hash 二分可以做到 \(\mathcal{O}(|t| + (|s| + k)k \log \max(|s|, |t|))\)
用后缀数组就可以做到 \(\mathcal{O}((|s| + |t|)\log (|s| + |t|) + (|s| + k)k)\)

代码写的 hash 二分,挺好写的。

submission

posted @ 2026-01-31 20:00  rizynvu  阅读(0)  评论(0)    收藏  举报