CF1987F Interesting Problem

lalaouye發表於2024-10-14

前兩個月被這題薄紗了,最後特判了幾個資料透過,現在來補。

先看簡單版本,注意到後面的刪除不會影響到前面的,而如果前面刪除了 \(x\) 次,那麼對於後面的任意一點 \(i\),只要是滿足 \(i-2x\le a_i\le i\) 這個條件就可以刪除。

結合資料範圍容易想到設 \(f_{l,r,x}\) 表示對於 \([l,r]\) 這個區間內,前面刪除了 \(x\) 次,能刪除最多的次數。

這個 dp 有兩個轉移,一個是 \(f_{l,r,x}\leftarrow f_{l+1,r-1,x}\),它的前提是在 \([l+1,r-1]\) 的區間中能夠刪完且 \(l\) 這個位置可以刪,一個是 \(f_{l,r,x}\leftarrow f_{l,k,x}+f_{k+1,r,x+f_{l,k,x}}\),然後對於字首取最大值即可。

這樣子可以做到 \(\mathcal{O}(n^4)\),可以透過簡單版本。

考慮最佳化。發現這樣子的區間 dp 直接最佳化是最佳化不了的,考慮最佳化狀態。觀察它的轉移,發現本質上第一個轉移是考慮能不能將區間 \([l,r]\) 中的數刪完,而第二個轉移則是合併能刪完與不能刪的區間。這啟發我們改變狀態,設 \(f_{l,r}\) 表示在區間 \([l,r]\) 前面至少要刪多少數才能使得該區間刪完所有數,轉移是與剛剛那個 dp 轉移類似的,而這個 dp 只需要 \(\mathcal{O}(n^3)\) 完成,最後使用一個 \(g_i\) 表示對於一個字首 \([1,i]\) 最多刪多少數,利用 \(f\) 簡單轉移即可。

總時間複雜度 \(\mathcal{O}(n^3)\),可以透過困難版本。

程式碼:

#include <bits/stdc++.h>
#define rep(i, l, r) for (int i = l; i <= r; ++ i)
#define rrp(i, l, r) for (int i = r; i >= l; -- i)
#define pii pair <int, int>
#define eb emplace_back
#define id(x, y) n * (x - 1) + y
#define ls p << 1
#define rs ls | 1
using namespace std;
constexpr int N = 800 + 5, M = (1ll << 31) - 1, P = 998244353;
constexpr double PI = acos (-1.0);
inline int rd () {
  int x = 0, f = 1;
  char ch = getchar ();
  while (! isdigit (ch)) {
    if (ch == '-') f = -1;
    ch = getchar ();
  }
  while (isdigit (ch)) {
    x = (x << 1) + (x << 3) + ch - 48;
    ch = getchar ();
  }
  return x * f;
}
int qpow (int x, int y) {
  int ret = 1;
  for (; y; y >>= 1, x = x * x % P) if (y & 1) ret = ret * x % P;
  return ret;
}
int f[N][N], g[N];
int n;
int a[N];
signed main () {
  for (int T = rd (); T; -- T) {
    n = rd ();
    rep (i, 1, n) a[i] = rd ();
    rep (i, 1, n) rep (j, 1, n) f[i][j] = 1e9, g[i] = 0;
    rep (i, 1, n - 1) {
      if (a[i] <= i && (i - a[i]) % 2 == 0) f[i][i + 1] = (i - a[i]) / 2;
    }
    rep (len, 3, n) {
      rep (l, 1, n - len + 1) {
        int r = l + len - 1;
        if (a[l] <= l && (l - a[l]) % 2 == 0) {
          int must = (l - a[l]) / 2;
          if (f[l + 1][r - 1] <= must) f[l][r] = must;
        } 
        rep (k, l, r - 1) {
          if (((k - l + 1) & 1) || ((r - k) & 1)) continue;
          f[l][r] = min (f[l][r], max (f[l][k], f[k + 1][r] - (k - l + 1) / 2));
        }
      }
    }
    rep (i, 1, n) {
      g[i] = g[i - 1];
      rep (j, 1, i - 1) {
        if (f[j][i] <= g[j - 1]) g[i] = max (g[i], g[j - 1] + (i - j + 1) / 2);
      }
    }
    printf ("%d\n", g[n]);
  }
}

相關文章