Educational Codeforces Round 168 (Rated for Div. 2)

写在前面

比赛地址: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, a_i + 1}\) 是否存在,以及是否有 \(i < f_{x, a_i + 1}\) 即可。

然后考虑如何预处理 \(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^2 n + q)\) 级别,但是常数很小跑的很快哈哈没被叉掉。如果树状数组上倍增或权值线段树上二分还能少个 \(\log\)

权值线段树二分见这发提交:https://codeforces.com/contest/1997/submission/274272323。另有根号分治的跑的飞快的做法,详见这发提交: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;
    std::string ans;
    if (a[i] + 1 > f[x].size() - 1) ans = "YES";
    else if (i < f[x][a[i] + 1]) ans = "YES";
    else ans = "NO";
    //赛时写的唐氏二分查询:
    // int p = std::upper_bound(f[x].begin(), f[x].end(), i) - f[x].begin() - 1;
    // std::cout << (p > a[i] ? "NO" : "YES") << "\n";
    std::cout << ans << "\n";
  }
  return 0;
}
/*
大爹们别叉我求你了呜呜呜呜我给你们磕头磕头磕头磕头磕头磕头
*/

F

我去提都看不懂

写在最后

参考:

学到了什么:

  • D:造极限数据;根据代码看 hack 数据。
  • E:某参数大小为 \(i\) 时贡献为 \(\frac{n}{i}\),则贡献总数为调和级数;查询操作直接暴力的复杂度与输入参数大小有关,考虑根号分治。

然后又是我最喜欢的夹带私货环节,我去晄轮大祭这个广播剧太几把有趣了我草全是活儿听得我感觉要飞起来了芜湖!

posted @ 2024-07-31 02:07  Luckyblock  阅读(380)  评论(1编辑  收藏  举报