Educational Codeforces Round 168 (Rated for Div. 2)

Luckyblock發表於2024-07-31

目錄
  • 寫在前面
  • A
  • B
  • C
  • D
  • E
  • F
  • 寫在最後

寫在前面

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

媽的寫 B 的時候唐完了漏看條件硬控二十分鐘導致後面所有題都加二十罰時呃呃要不然直接飛昇了幸好過完 C 之後狀態上來了看一眼直接秒了哈哈,現在這個排名穩能上紫了歐耶!

歐耶!

如果不被叉的話呃呃呃呃呃呃

A

簽到。

懶得找結論了於是直接大力模擬:

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
//=============================================================
std::string ans;
//=============================================================
void check(std::string s_) {
  int t1 = 2, t2 = 2;
  for (int i = 1; i < ans.length(); ++ i) {
    if (ans[i] != ans[i - 1]) t1 += 2;
    else ++ t1;
  }
  for (int i = 1; i < s_.length(); ++ i) {
    if (s_[i] != s_[i - 1]) t2 += 2;
    else ++ t2;
  }
  if (t2 > t1) ans = s_;
}
//=============================================================
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::string s; std::cin >> s;
    int n = s.length();
    ans = s;
    for (int i = 0; i <= n; ++ i) {
      for (char c = 'a'; c <= 'z'; ++ c) {
        check(s.substr(0, i) + c + s.substr(i, n - i));
      }
    }
    std::cout << ans << "\n";
  }
  return 0;
}

B

結論。

欽定了原圖中至多隻有一個連通塊,手玩下容易發現若可以變成三個連通塊當且僅當出現下述形狀並將標紅的 \(\text{o}\) 變成 \(\text{x}\),直接大力檢查即可。

\[\begin{aligned} \text{o}{\color{red}{\text{o}}}\text{o}\\ \text{xox} \end{aligned}\]

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 2e5 + 10;
//=============================================================
int n, belnum;
std::string map[3];
//=============================================================
int check(int x_, int y_) {
  if (map[1 + x_ % 2][y_] == '.' && map[x_][y_ - 1] == '.' && map[x_][y_ + 1] == '.')
    if (map[1 + x_ % 2][y_ - 1] == 'x' && map[1 + x_ % 2][y_ + 1] == 'x' ) 
      return 1;
  return 0;
}
//=============================================================
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 <= 2; ++ i) {
      std::cin >> map[i]; map[i] = "x" + map[i] + "x";
    }

    int ans = 0;
    for (int i = 1; i <= 2; ++ i) {
      for (int j = 1; j <= n; ++ j) {
        if (map[i][j] == '.') ans += check(i, j);
      }
    }
    std::cout << ans << "\n";
  }
  return 0;
}

C

貪心。

一個顯然的想法是令所有左括號儘可能與最近的右括號匹配。比如 _)_) 構造成 ()() 比構造成 (()) 更好。

於是考慮先將所有 _ 均視為右括號,然後對給定的字串用棧進行模擬做括號匹配,從而將原有的左括號全部匹配掉,且可以保證原有左括號均是和最近的右括號匹配的。此時棧內剩下的全部是“右括號”,且由給定字串一定對應合法括號序列可知,此時棧內括號數一定為偶數,且一定是 _) 交替的形式。於是僅需將它們相鄰兩兩匹配即可。

另外這題答案似乎可以直接統計左括號的數量來算,太牛逼!詳見題解:Educational Codeforces Round 168 (Rated for Div. 2) 題解 - zsc985246 - 部落格園

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
//=============================================================
int n;
std::string s;
//=============================================================
//=============================================================
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 >> s;
    LL ans = 0;
    std::stack<int> st;
    for (int i = 0; i < n; ++ i) {
      if (s[i] == '_') {
        if (!st.empty() && st.top() < 0) ans += i + st.top(), st.pop();
        else st.push(i);
      } else {
        if (s[i] == '(') st.push(-i);
        else st.push(i);
      }
    }
    while (!st.empty()) {
      int p1 = st.top(); st.pop();
      int p2 = st.top(); st.pop();
      ans += p1 - p2;
    }
    std::cout << ans << "\n";
  }
  return 0;
}

D

貪心,結論,二分答案

發現答案顯然是有單調性的,於是考慮二分答案,轉化為檢查根節點最終能否增加 \(\operatorname{lim}\)

節點 1 最多加幾次,等價於考慮其他節點最多能減幾次。於是考慮從 1 的子節點開始 dfs 檢查能否對該節點的子樹進行操作,使其經過 \(\operatorname{lim}\) 次操作後所有節點均為非負。

考慮 dfs 時下傳一個標記 \(\operatorname{tag}\),代表當前子樹內節點需要被減多少次,對於 1 的子節點有 \(\operatorname{tag} = \operatorname{lim}\)。對於每個節點 \(u\),若有 \(\operatorname{a_u}\ge \operatorname{lim}\) 則不會減到負值,不需要再額外對 \(u\) 進行加操作;否則令 \(\operatorname{tag} = \operatorname{tag} + (\operatorname{tag} - a_u)\) 表示需要先經過 \(\operatorname{tag} - a_u\) 次操作令 \(a_u\) 變為父節點的 \(\operatorname{tag}\)。到達葉節點時檢查 \(\operatorname{tag} \le a_u\) 是否成立即可,若所有葉節點均合法則檢查合法。

不過直接按照上述思路實現會 WA19,因為會被根為 \(10^9\),其餘點均為 0 時卡掉。此時按照上述 \(\operatorname{tag}\) 的變化量,每次下傳 \(\operatorname{tag}\) 時相當於令 \(\operatorname{tag}\) 乘 2 導致爆 LL。但是考慮到最終僅需與葉節點比較大小,於是額外維護 \(f_u\) 表示以 \(u\) 為根的子樹中葉節點的權值的最大值,若有 \(\operatorname{tag} > f_u\) 則直接不合法。

總時間複雜度 \(O(n\log v)\) 級別。

另外此題也有直接 dfs 並根據葉節點權值調整當前節點權值的智慧解法,詳見其他題解。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 5e5 + 10;
const LL kInf = 1e12;
//=============================================================
int n;
LL a[kN], maxa[kN];
std::vector<int> v[kN];
//=============================================================
void prepare(int u_) {
  maxa[u_] = 0;
  if (v[u_].empty()) maxa[u_] = a[u_];
  for (auto v_: v[u_]) {
    prepare(v_);
    maxa[u_] = std::max(maxa[u_], maxa[v_]);
  }
}
bool dfs(int u_, LL delta_) {
  if (maxa[u_] < delta_) return false;
  if (v[u_].empty()) return (a[u_] >= delta_);

  for (auto v_: v[u_]) {
    LL d = ((a[u_] >= delta_) ? delta_ : (1ll * delta_ + delta_ - a[u_]));
    if (!dfs(v_, d)) return false;
  }
  return true;
}
bool check(LL lim_) {
  if (maxa[1] < lim_ - a[1]) return false;

  for (auto v_: v[1]) if (!dfs(v_, lim_ - a[1])) return false;
  return true;
}
//=============================================================
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];
      v[i].clear();
    }
    for (int i = 2; i <= n; ++ i) {
      int fa; std::cin >> fa;
      v[fa].push_back(i);
    }
    prepare(1);

    LL ans = a[1];
    for (LL l = a[1] + 1, r = a[1] + kInf; l <= r; ) {
      LL mid = (l + r) / 2ll;
      if (check(mid)) {
        ans = std::max(ans, mid);
        l = mid + 1;
      } else {
        r = mid - 1;
      }
    }
    std::cout << ans << "\n";
  }
  return 0;
}

E

調和級數/根號分治,資料結構

發現對於每次詢問,相當於考慮 \(k=x\) 時,當列舉到怪物 \(i\) 時是否有血量大於 \(a_i\),於是僅需考慮如何快速求得此時的血量即可。

發現對於某個 \(k\) 直至遊戲結束血量最多增加到 \(\frac{n}{k}\),即此時整個過程中血量不同的段僅有 \(\frac{n}{k}\) 個。又 \(k\le n\),則對於所有 \(k\) 的血量不同段的數量是調和級數的,直接記錄他們複雜度可以接受。於是考慮預處理 \(f_{i, j}\) 表示當 \(k=i\) 時,在打怪過程中血量增加到 \(j\) 後打怪的起點,則 \(f\) 的有用狀態數僅有 \(O(n\ln n)\) 級別。

對於所有詢問 \(i, x\) 即可在 \(f_{x}\) 上二分得到打到怪物 \(i\) 時血量變為多少。

然後考慮如何預處理 \(f\)。發現若已知 \(f_{i, j}\),僅需找到以 \(f_{i, j}\) 為左端點的,包含 \(i\) 個不大於 \(j\) 的數的最小區間 \([f_{i, j}, r]\) 則可求得 \(f_{i, j+1} = r+1\)。這東西可以直接主席樹大力求,也可以考慮在正序列舉血量 \(j\) 再列舉 \(i\) 以調和級數地列舉所有狀態時,在此過程中動態地使用樹狀陣列維護區間內有多少個大於 \(j\) 的數,即可更新此時列舉到的所有狀態。

總時間複雜度 \(O(n\ln n \log n + q\log n)\) 級別,不得不用 vector 常數有點大呃呃。

另有根號分治的跑的飛快的做法,詳見這發提交:https://codeforces.com/contest/1997/submission/273578243

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 3e5 + 10;
//=============================================================
int n, q, a[kN];
std::vector<int> val[kN], f[kN];
//=============================================================
namespace bit {
  #define lowbit(x) ((x)&(-x))
  const int kNode = kN;
  int lim, t[kNode];
  void init(int lim_) {
    lim = lim_;
  } 
  void insert(int p_, int val_) {
    for (int i = p_; i <= lim; i += lowbit(i)) {
      t[i] += val_;
    }
  }
  int sum(int p_) {
    int ret = 0;
    for (int i = p_; i; i -= lowbit(i)) ret += t[i];
    return ret;
  }
  int query(int l_, int r_) {
    if (l_ > r_) return 0;
    return sum(r_) - sum(l_ - 1);
  }
}
void init() {
  bit::init(n);
  for (int i = 1; i <= n; ++ i) {
    val[a[i]].push_back(i);
    bit::insert(i, 1);
    f[i].push_back(0), f[i].push_back(1);
  }

  for (int round = 1; round <= n; ++ round) {
    for (int k = 1; round * k < n && ((int) f[k].size() > round); ++ k) {
      int p = f[k][round], next = n + 1;
      if (p == n + 1) continue;
      for (int l = p + k, r = n; l <= r; ) {
        int mid = (l + r) >> 1;
        if (bit::query(p, mid - 1) >= k) {
          next = mid;
          r = mid - 1;
        } else {
          l = mid + 1;
        }
      }
      f[k].push_back(next);
    }
    for (auto p: val[round]) bit::insert(p, -1);
  }

  for (int k = 1; k <= n; ++ k) f[k].push_back(n + 1);
}
//=============================================================
int main() {
  //freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  std::cin >> n >> q;
  for (int i = 1; i <= n; ++ i) std::cin >> a[i];
  init();

  while (q --) {
    int i, x; std::cin >> i >> x;
    int p = std::upper_bound(f[x].begin(), f[x].end(), i) - f[x].begin() - 1;
    std::cout << (p > a[i] ? "NO" : "YES") << "\n";
  }
  return 0;
}
/*
大爹們別叉我求你了嗚嗚嗚嗚我給你們磕頭磕頭磕頭磕頭磕頭磕頭
*/

F

我去提都看不懂

寫在最後

參考:

  • Educational Codeforces Round 168 (Rated for Div. 2) 題解 - zsc985246 - 部落格園
  • https://codeforces.com/contest/1997/submission/273578243

學到了什麼:

  • D:造極限資料;根據程式碼看 hack 資料。
  • E:某引數大小為 \(i\) 時貢獻為 \(\frac{n}{i}\),則貢獻總數為調和級數;查詢操作直接暴力的複雜度與輸入引數大小有關,考慮根號分治。

然後又是我最喜歡的夾帶私貨環節,我去晄輪大祭這個廣播劇太幾把有趣了我草全是活兒聽得我感覺要飛起來了蕪湖!

相關文章