CF1969F Card Pairing

lalaouye發表於2024-10-18

少有的獨自做出來的 *3000,還是很有成就感的!

集中注意力讀題,首先注意到每一時刻牌數為 \(k\),而牌的種類也為 \(k\),如果實際牌的種類數小於 \(k\),那麼是很簡單的情況,現在考慮實際牌的種類數等於 \(k\) 的情況。

觀察過程,首先發現如果有相同的牌直接丟就行,過程中還會出現沒有牌相同的情況,顯然這種情況每個牌的數量為 \(1\),啟發我們使用 dp,設 \(f_i\) 表示已經考慮完 \(i\) 張牌且當前沒有相同牌,能獲得的最多硬幣數。

首先呼之欲出的是 \(\mathcal{O}(n^2k^2)\) 的愚蠢做法。就是列舉我們刪哪兩張牌,再模擬出後面第一次出現沒有相同牌狀態的位置,並完成轉移。

這樣做是低效的。我們發現我們可以直接轉移,在轉移中變成判定問題,\(f_j\) 能轉移到 \(f_i\),當且僅當區間 \([i+1,j]\) 從當前狀態繼續操作完成後出現無相同牌狀態,且中途一直有相同牌。注意到刪相同牌不會改變奇偶性,而加入會,我們發現這個判定過程實際上是一個異或加入牌的過程,而一個區間能滿足條件當且僅當存在兩種牌出現個數為奇數,直接異或雜湊即可。

還有一種很煩人的情況,就是永遠都不會出現無相同牌的狀態了,這時的答案與實際牌的種類數小於 \(k\) 的類似,他的答案就是 \(\sum_{i=1}^k \lfloor \frac{s_i}{2}\rfloor\)\(s_i\) 表示第 \(i\) 種手牌的數量。然而,對於當前 \(f_i\) 的答案來說,\(s_i\) 是對於 \([i,n]\) 這個字尾算出來的,並且每個都會加一,因為當前每種牌各有一張。而這時我們要刪除兩張牌,顯然我們因儘可能刪除 \(s_i\) 為奇數的牌,因為這樣不會減少答案。列舉牌對顯然不優,但我們可以將判斷轉為簡單計數。

然後還有個細節就是起點問題,直接模擬找起點就行了。

總時間複雜度 \(\mathcal{O}(n^2)\)

程式碼:

#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) m * ((x) - 1) + (y)
#define ls p << 1
#define rs ls | 1
using namespace std;
constexpr int N = 1e3 + 5, P = 1e9;
constexpr double PI = acos (-1.0);
typedef unsigned long long ull;
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 n, k;
int a[N], b[N], f[N];
bitset <N> bit;
ull w[N];
unordered_map <ull, pii> mp;
unordered_map <ull, bool> mmp;
int s1[N][N], s[N];
int main () {
  // freopen ("1.in", "r", stdin);
  // freopen ("1.out", "w", stdout);
  n = rd (), k = rd ();
  rep (i, 1, n) a[i] = b[i] = rd ();
  sort (b + 1, b + n + 1);
  int kk = unique (b + 1, b + n + 1) - b - 1;
  if (kk < k) {
    rep (i, 1, n) ++ s[a[i]];
    int sum = 0;
    rep (i, 1, k) sum += s[i] / 2;
    cout << sum;
    return 0;
  }
  rep (i, 1, n) a[i] = lower_bound (b + 1, b + k + 1, a[i]) - b;
  mt19937_64 rnd (time (0));
  rep (i, 1, k) {
    w[i] = rnd ();
    rep (j, 1, i - 1) mp[w[i] ^ w[j]] = pii (i, j);
  }
  rrp (i, 1, n) {
    ull now = 0;
    rep (j, i, n) ++ s1[i][a[j]];
    int s2[3] = {0, 0, 0}, s3[2] = {0, 0}, sum = 0;
    rep (j, i, n) {
      now ^= w[a[j]];
      if (mp.find (now) != mp.end () && ! mmp[now]) {
        pii p = mp[now];
        mmp[now] = 1;
        f[i - 1] = max (f[i - 1], f[j] + (j - i + 1) / 2 - 1);
        int x = s1[i][p.first], y = s1[i][p.second];
        if ((x & 1) ^ (y & 1)) ++ s2[1];
        else if (x & 1) ++ s2[0]; else ++ s2[2];
      }
    }
    now = 0;
    rep (j, i, n) {
      now ^= w[a[j]];
      mmp[now] = 0;
    }
    rep (j, 1, k) sum += (s1[i][j] + 1) / 2, ++ s3[s1[i][j] & 1];
    if (s3[1] * (s3[1] - 1) / 2 > s2[0]) f[i - 1] = max (f[i - 1], sum - 2);
    if (s3[0] * s3[1] > s2[1]) f[i - 1] = max (f[i - 1], sum - 1);
    if (s3[0] * (s3[0] - 1) / 2 > s2[2]) f[i - 1] = max (f[i - 1], sum);
  }
  int ans = -1;
  rep (i, 1, n) {
    if (bit[a[i]] == 1) bit[a[i]] = 0;
    else bit[a[i]] = 1;
    if (bit.count () == k) {
      ans = max (ans, (i - k) / 2 + f[i]);
      break;
    }
  }
  if (ans == -1) {
    rep (i, 1, n) ++ s[a[i]];
    int sum = 0;
    rep (i, 1, k) sum += s[i] / 2;
    cout << sum; return 0;
  }
  cout << ans;
}

相關文章