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\) 的位置的最小代价。
考虑怎么转移。
-
这个位置是 B 且钦定这个 B 不在任何一个需要重排的段内。
那么这个 B 的位置一定不会发生改变,于是有 \(f_i = f_{i - 1}\)。 -
钦定这个位置为一个需要重排的段的段尾。
考虑找到段头 \(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;
}