Loading

Solution - Atcoder AGC066D A Independent Set

首先考虑如果知道了 A 最后的位置,最后的答案是多少。
\(s_{1\sim k}, t_{1\sim k}\) 分别为 \(S\) 中 A 的位置和最后 A 的位置。
因为如果交换两个 A 或 B 肯定是不优的,因为这并没有做出实质上的改变,所以说一定是 \(s_i\) 最后移到 \(t_i\) 的位置上(不然会产生交叉,不优)。
那么从 \(s_i\) 移动到 \(t_i\),对应的代价就为 \(\sum\limits_{i = \min\{s_i, t_i\}}^{\max\{s_i, t_i\} - 1} x_i\),但这个 \(\sum\) 肯定是不希望看到的,于是定义 \(x'_i = \sum\limits_{j = 1}^{i - 1} x_i\),那么代价就可以写作 \(|x'_{s_i} - x'_{t_i}|\)

下文的 \(x\) 指代上面的 \(x'\)

考虑到限制 A 不能相邻在形式上有什么特殊。
考虑从反面考虑,A 相邻的不能是 A 就意味着 A 相邻的一定是 B。

于是最终的序列一定可以被刻画成许多 AB 和 B 相连接的情况。
但是不好的一点是末尾的 A 似乎太特殊了,于是考虑手动添加一个 B 并钦定其不能移动(代价为 \(+\infty\))。

接下来因为目标是 A,于是考虑最后串的 AB 会有什么性质。
如果去手玩一下,应该能得到一个结论:每次一定是选 A 和 B 个数相同的段然后变为 AB 不断复制的形式。
这也是比较好理解的,因为如果找到了这样的段还是要把段内的 A 移到段外,最后肯定会多交换一段距离,对应代价肯定更大。

那么找到了这个性质,就可以考虑 DP 了。
\(f_i\) 为考虑了 \(S\) 的前 \(i\) 个字符的位置分配且占用的就是 \(1\sim i\) 的位置的最小代价。
考虑怎么转移。

  1. 这个位置是 B 且钦定这个 B 不在任何一个需要重排的段内。
    那么这个 B 的位置一定不会发生改变,于是有 \(f_i = f_{i - 1}\)

  2. 钦定这个位置为一个需要重排的段的段尾。
    考虑找到段头 \(j(j < i)\),那么可以考虑把 A 当作 \(1\),B 当作 \(-1\) 做前缀和,那么必然有 \(s_{j - 1} = s_i\)
    于是有 DP 转移 \(f_i = f_{j - 1} + \operatorname{cost}(j, i)\)
    其中 \(\operatorname{cost}(l, r)\) 指的是 \(l, r\) 内重排的代价。

    但是现在有个问题就是 \(j\) 的数量可能过多了。
    考虑到如果有 \(k < j\) 满足 \(k\) 也可以作为段头,实际上有 \(\operatorname{cost}(k, j - 1) + \operatorname{cost}(j, i) = \operatorname{cost}(k, i)\)
    这是因为 \([k, j - 1]\) 内的 A 和 B 数量也是相同的,所以重排不会影响到 \([j, i]\)
    所以发现 \(f_{k - 1} + \operatorname{cost}(k, j - 1)\) 会转移到 \(f_{j - 1}\),就也能让 \(f_{k - 1} + \operatorname{cost}(k, j - 1) + \operatorname{cost}(j, i) = f_{k - 1} + \operatorname{cost}(k, i)\) 转移到 \(f_i\) 了。
    所以说只需要考虑合法的最大的一个 \(j\) 转移过来就可以了。

    接下来还有个问题,就是 \(\operatorname{cost}(l, r)\) 怎么算。
    因为钦定了最终的形式是 AB 重复,于是可以知道最后的 A 肯定是在 \(l, l + 2, \cdots, r - 1\) 的位置。
    但是好像还是不是很好做,于是去发掘性质,但是 \(\operatorname{cost}\) 本身已经没啥可参考了,于是考虑这个区间 \([l, r]\) 具有的性质。
    根据前面转移得到的信息,这个 \([l, r]\) 一定不会在中间存在一个断点使得断掉后两部分的 AB 数量相同。
    那么实际上说明,对于 \([l, r]\) 的每一个前缀,A 的数量都比 B 多,或者是每个前缀 B 数量都比 A 多。
    这是因为每次前缀和的变化是 \(\pm 1\) 的,如果存在小于等于就必然有等于,存在等于就必然有断点,矛盾。
    那么这又告诉的信息是要么 \(s_1 \le l, s_2\le l + 2, \cdots\),要么 \(s_1\ge l, s_2\ge l + 2, \cdots\),即每个 \(s_i\)\(t_i\) 的大小关系(\(\le, \ge\))其实是相同的。
    那么对于 \(\sum\limits_{i = 1}^k |x_{s_i} - x_{t_i}|\) 的这个绝对值就可以拆开,套在外面变成 \(|\sum\limits_{i = 1}^k x_{s_i} - \sum\limits_{i = 1}^k x_{t_i}|\)
    这就好做了,对于 \(s_i\) 就维护一个关于 A 的前缀和。因为 \(t_i \bmod 2\) 是一样的,再维护一个关于下标 \(\bmod\ 2\) 的前缀和即可。

最后时间复杂度 \(\mathcal{O}(n)\)

#include<bits/stdc++.h>
using ll = long long;
constexpr int maxn = 1e6 + 10;
int n;
char s[maxn];
ll x[maxn], sumb[maxn], suma[maxn], f[maxn];
int w[maxn * 2];
inline void solve() {
   scanf("%d%s", &n, s + 1);
   for (int i = 2; i <= n; i++) {
      scanf("%lld", &x[i]);
      x[i] += x[i - 1];
   }
   s[++n] = 'B', x[n] = 1e18;
   memset(w, -1, sizeof(int) * (n + n + 1));
   memset(f, 0x3f, sizeof(ll) * (n + 1));
   f[0] = 0ll, w[n] = 0;
   for (int i = 1, now = n; i <= n; i++) {
      suma[i] = suma[i - 1];
      if (i > 1) {
         sumb[i] = sumb[i - 2] + x[i];
      }
      if (s[i] == 'A') {
         suma[i] += x[i];
         now++;
      } else {
         f[i] = f[i - 1];
         now--;
      }
      if (~ w[now]) {
         int j = w[now];
         f[i] = std::min(f[i], f[j] + std::abs((sumb[i - 1] - (j ? sumb[j - 1] : 0ll)) - (suma[i] - suma[j])));
      }
      w[now] = i;
   }
   printf("%lld\n", f[n]);
}
int main() {
   for (int T, _ = scanf("%d", &T); T--; ) {
      solve();
   }
   return 0;
}
posted @ 2024-11-22 08:39  rizynvu  阅读(4)  评论(0编辑  收藏  举报