Codeforces Round 961 (Div. 2)

Luckyblock發表於2024-07-24

目錄
  • 寫在前面
  • A
  • B1 & B2
  • C
  • D
  • 寫在最後

寫在前面

比賽地址:https://codeforces.com/contest/1995

上了一百多分,爽!要是把 D 衝出來就直接紫了呃呃然而失敗!爭取下把上紫!

A

簽到。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
//=============================================================
//=============================================================
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  int T; std::cin >> T;
  while (T --) {
    int n, k; std::cin >> n >> k;
    if (k == 0) std::cout << 0 << "\n";
    else if (k && k <= n) std::cout << 1 << "\n";
    else {
      int ans = 1;
      k -= n;

      for (int i = n - 1; i; -- i) {
        if (k > 0) ++ ans, k -= i;
        if (k > 0) ++ ans, k -= i;
      }
      std::cout << ans << "\n";
    }
  }
  return 0;
}

B1 & B2

列舉。

不太懂分 B1B2 什麼意義、、、過了 B1 兩分鐘就把 B2 過了呃呃也可能是我 B1 沒看題不知道暴力怎麼寫哈哈

已知每種權值的出現次數,於是僅需考慮對於兩個分別出現了 \(c_1, c_2\) 的兩個權值 \(a, a+1\) 使用代價 \(m\) 可以獲得的最大價值。

發現將選擇的一個 \(a\) 替換為 \(a+1\) 會且僅會使價值 +1,則一個顯然的想法是先用 \(a\) 湊,剩餘部分用 \(a+1\) 湊,然後檢查此時是否:

  • 選擇了至少一個 \(a\)
  • 選擇 \(a+1\) 的數量不大於 \(c_2\)
  • 代價之和不小於 \(m\)

然後考慮將盡可能多的 \(a\) 替換為 \(a+1\) 即可,具體討論過程詳見程式碼。

以下為 B2 程式碼。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 2e5 + 10;
std::map<int, int> cnt; 
//=============================================================
int n, a[kN];
LL m;
//=============================================================
//=============================================================
int main() {
  //freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  int T; std::cin >> T;
  while (T --) {
    std::cin >> n >> m;
    cnt.clear();
    for (int i = 1; i <= n; ++ i) std::cin >> a[i];
    for (int i = 1; i <= n; ++ i) {
      int c; std::cin >> c;
      cnt[a[i]] = c;
    }
    
    LL ans = 0;
    for (int i = 1; i <= n; ++ i) {
      int c1 = cnt[a[i]], c2 = cnt[a[i] + 1];
      LL temp = 1ll * c1 * a[i] + c2 * (a[i] + 1);
      if (temp <= m) ans = std::max(ans, temp);

      LL k1 = std::min(1ll * c1, m / (a[i]));
      temp = k1 * a[i];
      
      LL k2 = std::min(1ll * c2, (m - temp) / (a[i] + 1));
      temp += k2 * (a[i] + 1);

      if (k1 && k2 < c2 && temp < m) {
        LL delta = std::min(std::min(k1, c2 - k2), (m - temp));
        k1 -= delta, k2 += delta, temp += delta;
      }
      ans = std::max(ans, temp);
    }
    std::cout << ans << "\n";
  }
  return 0;
}

C

維護技巧。

看到這題笑死我了太典了實在是

發現可以直接從前往後操作,每次將當前位置的數調整為最小的不小於前一個數的數即可。然而大力平方之後會很大,沒法直接比較大小那麼沒法直接做。

發現 \(a_i\) 平方 \(c_i\) 次後為 \(a^{2^{c_i}}\),於是僅需考慮如何找到最小的 \(c'_i\),使得:

\[\large a_i^{2^{c'_i}} \ge a_{i -1}^{2^{c_{i-1}}} \]

這個冪次的形式太典了,取個 \(\log\)

\[2^{c'_i}\log a_i \ge 2^{c_{i-1}} \log a_{i -1} \]

發現有 \(2\) 的冪還是很大,不太好直接比較,那麼再取個 \(\log\)

\[c'_i\log 2 + \log\log a_i \ge c_{i-1}\log 2 +\log \log a_{i -1} \]

這下就好做了。每次二分或者直接大力算出來最小的 \(c'\) 即可,若求不出來則無解。賽時懶得多想了寫了二分,爽!

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 2e5 + 10;
const double eps = 1e-9;
//=============================================================
int n, a[kN]; 
LL ans, f[kN];
//=============================================================
bool notsmaller(LL a_, LL f1_, LL b_, LL f2_) {
  double lga = log10(1.0 * a_), lgb = log10(1.0 * b_);
  double lglga = log10(lga), lglgb = log10(lgb);
  double lg2 = log10(2.0);

  return (f1_ * lg2 + lglga + eps >= f2_ * lg2  + lglgb);
}
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  int T; std::cin >> T;
  while (T --) {
    std::cin >> n;
    for (int i = 1; i <= n; ++ i) std::cin >> a[i];
    for (int i = 1; i <= n; ++ i) f[i] = 0;

    ans = 0;
    for (int i = 2; i <= n; ++ i) {
      if (notsmaller(a[i], 0, a[i - 1], f[i - 1])) continue;
      for (LL l = 1, r = 1e10; l <= r; ) {
        LL mid = (l + r) >> 1ll;
        if (notsmaller(a[i], mid, a[i - 1], f[i - 1])) {
          f[i] = mid;
          r = mid - 1;
        } else {
          l = mid + 1;
        }
      }
      if (!f[i]) {
        ans = -1;
        break;
      }
      ans += f[i];
    }
    std::cout << ans << "\n";
  }
  return 0;
}

D

狀壓 DP。

江隊說是套路媽的,場上光想著怎麼 check 一個狀態是否合法了還能這麼搞太牛逼了。

這個資料範圍瘋狂暗示需要狀壓,於是考慮狀壓 DP 求有哪些選擇結尾的狀態是合法的。

發現題目給定限制,等價於對於所有長度為 \(k\) 的子區間,要求這些子區間中至少有一個字元為結尾字元;且最後一個字元一定為結尾字元。則對於每一個子區間中所有字元,在任一合法的選擇結尾的方案中不能同時不出現

於是一個顯然的想法是考慮在結尾字元中,不存在哪些字元會使得方案非法。記 \(f_{s}\) 表示不存在的結尾字元的狀態 \(s\) 時是否合法,初始化:

  • 對於每一長為 \(k\) 的子區間,求其中出現的字元狀態集合 \(s'\),則 \(f_{s'} = \text{false}\)
  • 僅包含最後一個字元 \(s_{n}\) 的狀態 \(s''\),有 \(f_{s''} = \text{false}\)
  • 除此之外所有其他集合 \(s\) 均有 \(f_{s} = \text{true}\)

然後按照不存在字元數量列舉集合 \(1\sim 2^{c} - 1\),考慮當前集合是否包含一個非法的子集即可,即有轉移:

\[\forall 1<s\le 2^{c}-1,\ f_{s} = \operatorname{AND}\limits_{2^i\in s} f_{s - 2^i} \]

最後對所有 \(f_{s} = \text{true}\) 的狀態統計其中 0 的個數取最小值即為答案。

總時間複雜度 \(O(k + c\times 2^c)\) 級別。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 1e6 + 10;
const int kInf = 1e9 + 2077;
const int kC = 20;
//=============================================================
int n, c, k, all, ans;
int cnt[kC];
bool can_delete[kN];
std::string s;
//=============================================================
void init() {
  all = (1 << c) - 1, ans = c;
  for (int i = 0; i <= all; ++ i) can_delete[i] = 1;
  for (int i = 0; i < c; ++ i) cnt[i] = 0;
  for (int i = 0; i < k; ++ i) ++ cnt[s[i] - 'A'];
  for (int l = 0, r = k - 1; r < n; ++ l, ++ r) {
    int j = 0;
    for (int i = 0; i < c; ++ i) if (cnt[i]) j |= (1 << i);
    can_delete[j] = 0;
    if (r + 1 < n) -- cnt[s[l] - 'A'], ++ cnt[s[r + 1] - 'A'];
  }

  can_delete[1 << (s[n - 1] - 'A')] = 0;
}
void solve() {
  for (int i = 1; i < all; ++ i) {
    int cnt0 = 0;
    for (int j = 0; j < c; ++ j) {
      if (i >> j & 1) can_delete[i] &= can_delete[i ^ (1 << j)];
      else ++ cnt0;
    }
    if (can_delete[i]) ans = std::min(ans, cnt0);
  }
}
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  int T; std::cin >> T;
  while (T --) {
    std::cin >> n >> c >> k;
    std::cin >> s;
    init();
    solve();
    std::cout << ans << "\n";
  }
  return 0;
}

寫在最後

學到了什麼:

  • D:預處理狀態後自底向下遞推。

相關文章