Solution - Atcoder AGC066D A Independent Set

rizynvu發表於2024-11-22

首先考慮如果知道了 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;
}

相關文章