【暑期多校补题记录】2025牛客多校(3)(4) 杭电多校(2)(3)

本文发布于博客园,会跟随补题进度实时更新,若您在其他平台阅读到此文,请前往博客园获取更好的阅读体验。
跳转链接:https://www.cnblogs.com/TianTianChaoFangDe/p/19007314

开题情况

7.22牛客多校3 : 5题 - ADEFJ
7.24牛客多校4 : 1题 - F
7.21杭电多校2 : 4题 - 2、6、8、9
7.25杭电多校3 : 4题 - 2、7、8、12

暑期多校第二周,这周打得有点不尽如人意,主要是好几场的mid题就开始卡了,尤其是牛客多校4,被自己一个很低级的错误葬送了B题,还需多练!

本人个人补题情况

7.22牛客多校3 : 3题 - BEH
7.24牛客多校4 : 2题 - BG
7.21杭电多校2 : 3题 - 7、9、12
7.25杭电多校3 : 2题 - 4、9

有些题场上虽然过了,但一看并非最优解,就也纳入补题阵列了。

牛客多校3补题

B - Bitwise Puzzle

赛时试图枚举所有初始状态来求解,但还是一WA再WA。
首先 (b) 可以进行按位右移操作,所以 (b) 是可以变成 (0) 的,那么,我们就有一个大致的方向了:用 (b) 来改 (a),把 (a) 改成 (c),然后把 (b) 异或上 (a)。
当 (a) 和 (b) 不都为 (0) 的时候,如果 (a) 和 (b) 的最高位不同,我们一定可以通过一次 (a oplus b),来把 (a) 和 (b) 的最高位变成一样的(因为最高位不等的话,在该位,一定有一个 (1) 一个 (0),异或一下就可以把 (0) 改成 (1)),记这个最高位为 (k)。
由于我们要把 (b) 变成 (0),所以 (b) 的最高位 (1) 会逐步右移,就足以修改掉 (a) 的 (leq k) 的位了,因此对于 (a) 的 (leq k) 的位,只需要通过把 (b) 逐步右移,对于 (b) 当前的最高位,比对 (a) 和 (c) 在该位是否相等,如果不相等,就用一次异或操作进行修改。由于 (b) 的最高位往左全都是 (0),所以修改不会对 (a) 已修改的位造成影响。
那如果 (c) 的最高位 (> k) 呢?(b) 右移逐步修改是修改不到更高的位的,那么我们可以换个角度,既然 (b) 不能左移,那就让 (a) 左移,我们用第 (k) 位去修改 (a) 的第 (k) 位,然后通过左移操作把修改的位往左运输过去,不就行了吗。
因此,我们的操作顺序应该是:

  • 如果 (a) 和 (b) 最高位不等,那么用一次 (a oplus b) 把最高位对齐。
  • 通过 (a oplus b) 和 (a << 1) 把 (> k) 的位修改得和 (c) 一样。
  • 通过 (a oplus b) 和 (b >> 1) 把 (leq k) 的位修改得和 (c) 一样。
  • 用 (a oplus b) 把 (b) 改成 (c)。

计算一下操作次数:第一步最多执行 (1) 次操作,第二步和第三步每次修改最多有 (2) 个操作参与,总的位数最多为 (31) 位,所以最多 (31 * 2 = 62) 次,最后一步执行 (1) 次操作,加起来刚好 (64) 次,非常稳定!
再看一下上面这个做法成立的条件,我们需要把 (a) 和 (b) 的最高位对齐,因此,至少要有一个非 (0),如果两个都是 (0),则没有任何的修改机会,当且仅当 (c = 0) 的时候有解,否则无解。

点击查看代码
#include <bits/stdc++.h> #define int long long  using i64 = long long;  void solve() {     int a, b, c;std::cin >> a >> b >> c;      if(a == b && b == 0) {         if(c == 0) {             std::cout << 0 << "nn";         } else {             std::cout << -1 << 'n';         }         return;     }      std::vector<int> op;      if(std::__lg(a) != std::__lg(b)) {         if(std::__lg(a) < std::__lg(b)) {             a ^= b;             op.push_back(3);         } else {             b ^= a;             op.push_back(4);         }     }      int ch = std::__lg(c) - std::__lg(a);     int now = std::__lg(a);     if(ch > 0) {         for(int i = 0;std::__lg(a) < std::__lg(c);i ++) {             if((c >> (std::__lg(c) - i) & 1) != (a >> now & 1)) {                 a ^= b;                 op.push_back(3);             }             a <<= 1;             op.push_back(1);         }     }      for(int i = now;i >= 0;i --) {         if((c >> i & 1) != (a >> i & 1)) {             a ^= b;             op.push_back(3);         }         b >>= 1;         op.push_back(2);     }      while(b) {         b >>= 1;         op.push_back(2);     }      b ^= a;     op.push_back(4);      std::cout << op.size() << 'n';     for(auto &x : op) {         std::cout << x << ' ';     }      std::cout << 'n'; }  signed main() {     std::ios::sync_with_stdio(false);     std::cin.tie(nullptr);     std::cout.tie(nullptr);          int t = 1;     std::cin >> t;          while (t--) {         solve();     }          return 0; }  

E - Equal

这个题赛时队友想的。
首先,如果 (n) 为奇数,那么一定可行,因为对除了某个数字外的其它的所有数字(一定是偶数个)都做一个相同的操作,等于对这个数字做一个单点的反向操作,那么一定可以把所有数字都变成一样的。(类似于上半年南昌邀请赛的一个题)
如果 (n) 为 (2),特殊判断一下,当且仅当两个数字相同的时候才可行,因为此时只能对这两个数字操作,且操作都相同。
如果 (n) 为其它偶数,那么,只需要判断,各个质数在这些所有数中的出现次数,是否为偶数次,如果为偶数次,则一定可行,因为每次操作对质数次数变化都是偶数次,如果一开始次数为奇数次,那么最后必然会有孤立的数,并且不能像奇数的情况那样,通过修改其它的来看作反向修改自己,因为此时除了孤立出来的那个数,剩下的数的总数是偶数个。

处理方法,赛时选择对每个数进行唯一分解来计数,通过素数剪枝卡过去了,此做法很极限,瓶颈在于唯一分解。
那么再观察一下这个题要我们干嘛,质数出现次数为偶数,那什么方法很适合用来统计信息出现的奇偶性呢?
那不就是异或哈希嘛!
我们通过给每个质数随机一个哈希值,然后在素筛的过程中通过异或处理出每个数的哈希值,由于次数为偶数的时候,异或值为 (0),因此,判断数组中所有分解出来的质数的出现次数是否都为偶数,也就是判断数组所有数的哈希值异或起来的值是否为 (0)。

时间复杂度:(O(nlog(logn)))

点击查看代码
#include <bits/stdc++.h> #define int long long  const int N = 5e6 + 9; bool notprime[N]; std::vector<int> prime; bool vis[N]; int hash[N];  std::mt19937_64 rnd(std::chrono::steady_clock::now().time_since_epoch().count());  int ran(int l, int r)  {     return rnd() % (r - l + 1) + l; }  void init() {     notprime[0] = notprime[1] = true;      for(int i = 2;i < N;i ++) {         if(!notprime[i])prime.push_back(i);          for(int j = 0;j < prime.size() && prime[j] * i < N;j ++) {             notprime[prime[j] * i] = true;             if(i % prime[j] == 0) {                 break;             }         }     }      for(int i = 0;i < prime.size();i ++) {         hash[prime[i]] = ran(1e9, 1e18);         for(int j = prime[i] * 2;j < N;j += prime[i]) {             int tmp = j;             while(tmp % prime[i] == 0) {                 tmp /= prime[i];                 hash[j] ^= hash[prime[i]];             }         }     } }  void solve() {     int n;std::cin >> n;      std::vector<int> a(n);      for(auto &x : a) {         std::cin >> x;     }      if(n & 1) {         std::cout << "YESn";         return;     }      if(n == 2) {         if(a[0] == a[1]) {             std::cout << "YESn";         } else {             std::cout << "NOn";         }         return;     }      int ans = 0;     for(auto &x : a) {         ans ^= hash[x];     }      std::cout << ((ans == 0) ? "YESn" : "NOn"); }  signed main() {     std::ios::sync_with_stdio(false);     std::cin.tie(nullptr);     std::cout.tie(nullptr);      init();      int t = 1;std::cin >> t;     while(t --) {         solve();     }      return 0; } 

H - Head out to the Target

赛时磕 B 去了,没看这题,现在回想起来应该和队友一起磕这题的。

这个题首先要明确一点,如果我们假设一个结点 (x) 是最后答案所在点,那么必定是从根沿着路径走下来,不会走到别的地方去(也就是把别的路切了),不然一定不优。
那么其实我们需要关注的,就是到某个时间点的时候,目标到根的路径上的所有结点,也就是目标的所有祖先结点,是否都有可能在最优情况下走过。
当一个目标出现时,只需要把它的最近的祖先结点往下扩展一格就行了,一个祖先结点是可能走过的,那么这个目标出现时,完全可以认为当前棋子就在这个祖先结点上,因为在其它结点的话,勾引过来到的位置,也一定是之前已经到过的,所以每次最多扩展一格。
只需要判断目标在每个结点时,该结点是否有可能走过即可(因为也可以切断它下面的边避免它被勾引走)。
这个祖先结点可以使用倍增进行查找,单次时间复杂度为 (O(logn)),由于每个点最多被扩展一次,所以最多扩展 (n) 次,然后就一定有解了。
时间复杂度:(O(nlogn))。

点击查看代码
#include <bits/stdc++.h> #define int long long  const int N = 1e7 + 9;  void returm() {     int n, k;std::cin >> n >> k;      std::vector<std::array<int, 30>> fa(n + 1, std::array<int, 30>{});      for(int i = 2;i <= n;i ++) {         std::cin >> fa[i][0];     }      for(int k = 1;k < 30;k ++) {         for(int i = 1;i <= n;i ++) {             fa[i][k] = fa[fa[i][k - 1]][k - 1];         }     }      std::vector<int> vis(n + 1);     vis[1] = true;     vis[0] = true;      auto get = [&](int st) -> int {         for(int k = 29;k >= 0;k --) {             if(vis[fa[st][k]])continue;             st = fa[st][k];         }          return st;     };      int ans = -1;     while(k --) {         int u, l, r;std::cin >> u >> l >> r;         if(ans != -1)continue;         if(vis[u]) {             ans = l;             continue;         }             for(int i = l;i <= r;i ++) {             int now = get(u);             vis[now] = true;             if(now == u) {                 ans = i;                 break;             }         }     }      std::cout << ans << 'n'; }  signed main() {     std::ios::sync_with_stdio(false);     std::cin.tie(nullptr);     std::cout.tie(nullptr);      int t = 1;//std::cin >> t;     while(t --) {         returm();     } } 

牛客多校4补题

B - BlindAlley

赛时战犯的一题,由于最大值自底向上转移的时候循环写反了,导致赛时没能开出,记录于此,警钟长鸣!

还是写写思路吧,这个题,其实关注点总结下来就一句话:一个点,是否有可能在某一时刻,以为能向右走过去走到终点,于是向右走到了这个点,但这个点实际上是无法抵达终点的,同时也没有回头路了。
翻译一下题意如下:

  1. 在某个点以为能向右走过去,翻译一下其实就是:当前列为 (j),向右走了过后,该右点能向右到达的最远点的列数 (geq j + k)。
  2. 这个点实际上是无法抵达终点,翻译一下,其实就是:从重点出发按题目规则反向走走不到这个点。

因此,思路就很简单了。
首先从起点跑一遍BFS再配合一次从右往左的简单DP转移最值,预处理一下每个点可以到达的最远列。
然后从重点跑一遍BFS,找出所有可以抵达的点。
然后再从起点跑一遍BFS,不过这次和第一次不一样,这次在走点的时候,要判断一下是否满足上述第1条题意翻译,然后对于可以抵达的点,判断一下它是不是可以抵达终点的点,如果不是,那就直接输出 Yes 即可。

点击查看代码
#include <bits/stdc++.h> #define int long long  const int N = 1e7 + 9;  int dx[] = {-1, 1, 0, 0}; int dy[] = {0, 0, 1, -1};  void returm() {     int n, m, k;std::cin >> n >> m >> k;      std::vector a(n, std::vector<char>(m));      for(auto &x : a) {         for(auto &c : x) {             std::cin >> c;         }     }      std::vector mx(n, std::vector<int>(m));      std::queue<std::pair<int, int>> q;      q.push({0, 0});     std::vector vis(n, std::vector<int>(m));      auto inmp = [&](int x, int y) -> bool {         return x >= 0 && x < n && y >= 0 && y < m;     };      mx[0][0] = 0;     while(q.size()) {         auto [x, y] = q.front();         q.pop();          for(int i = 0;i < 3;i ++) {             int nx = x + dx[i];             int ny = y + dy[i];              if(!inmp(nx, ny))continue;             if(a[nx][ny] == '1')continue;             if(vis[nx][ny])continue;              mx[nx][ny] = std::max(y, ny);             vis[nx][ny] = true;             q.push({nx, ny});         }     }      for(int j = m - 1;j >= 0;j --) {         if(j + 1 < m) {             for(int i = 0;i < n;i ++) {                 if(a[i][j + 1] == '0')mx[i][j] = std::max(mx[i][j], mx[i][j + 1]);             }         }          for(int i = 0;i < n;i ++) {             if(a[i][j] == '1')continue;             if(i - 1 >= 0 && a[i - 1][j] == '0')mx[i][j] = std::max(mx[i][j], mx[i - 1][j]);         }          for(int i = n - 1;i >= 0;i --) {             if(a[i][j] == '1')continue;             if(i + 1 < n && a[i + 1][j] == '0')mx[i][j] = std::max(mx[i][j], mx[i + 1][j]);         }     }      std::vector v(n, std::vector<int>(m));     v[0][m - 1] = true;     q.push({0, m - 1});      while(q.size()) {         auto [x, y] = q.front();         q.pop();          for(int i = 0;i < 4;i ++) {             if(i == 2)continue;             int nx = x + dx[i];             int ny = y + dy[i];             if(!inmp(nx, ny))continue;             if(a[nx][ny] == '1')continue;             if(v[nx][ny])continue;                         v[nx][ny] = true;             q.push({nx, ny});          }     }      std::vector u(n, std::vector<int>(m));     q.push({0, 0});     u[0][0] = true;      while(q.size()) {         auto [x, y] = q.front();         q.pop();          if(!v[x][y]) {             std::cout << "Yesn";             return;         }          for(int i = 0;i < 3;i ++) {             int nx = x + dx[i];             int ny = y + dy[i];             if(!inmp(nx, ny))continue;             if(a[nx][ny] == '1')continue;             if(u[nx][ny])continue;             if(mx[nx][ny] < y + k)continue;              u[nx][ny] = true;             q.push({nx, ny});         }     }      std::cout << "Non"; }  signed main() {     std::ios::sync_with_stdio(false);     std::cin.tie(nullptr);     std::cout.tie(nullptr);      int t = 1;std::cin >> t;     while(t --) {         returm();     } } 

G - Ghost intheParentheses

看到括号对,作为一个栈匹配的经典模型,常见的思路就是转化为前缀问题,对 ( 赋值为 (1),对 ) 赋值为 (-1)。
题目说对于未知的括号对,要让它们的填法是唯一的,来满足合法的括号序列。
那考虑一下,什么样的括号对,如果未知,其填法不是唯一的呢?
对于一个合法的括号对序列,应该满足它的任何一个前缀和都 (geq 0),如果交换一个左括号和一个右括号,该条件仍然满足,那么就说明不唯一!
因此,唯一的条件是,交换任何一个左括号和任何一个右括号,都会导致存在一个前缀值 (< 0)。
回到这个题目,我们对于每一个点 (i),找到右边最近的一个满足前缀值 (= 1) 的位置 (j),这时候, (i) 左侧的左括号,都可以未知,且只能是左括号未知,(j) 右侧的右括号,都可以未知,且只能是右括号未知,因为此时交换左边未知左括号和右边未知的右括号,必然导致 (j) 处的前缀值变成 (-1 < 0)。
同时,考虑 (i) 到 (j) 之间的字符都已知,因为 (i) 到 (j) 之间的字符如果未知,都会对 (j) 的位置造成影响,不便于计数。
由于选出的未知字符一定是左边很多左括号,右边很多右括号,不会出现先右括号再左括号的情况(出现这种情况了则可以交换他俩了,前缀值只会增大),这样的话,为了不重不漏,我们可以以每个左括号为分界点来进行计数,一定可以做到不重不漏。
但其实还是会漏掉一种情况,由于在上面的枚举过程中,我们保证了每种情况至少有一个左括号是不选为未知的,所以所有左括号都被选为未知的情况会被漏掉,并且这时候一定唯一,所以最后再加上这种情况即可。

点击查看代码
#include <bits/stdc++.h> #define int long long  const int M = 998244353;  int power(int n, int m) {     int res = 1;     while(m) {         if(m & 1)res = res * n % M;         n = n * n % M;         m >>= 1;     }     return res; }  int inv(int x) {     return power(x, M - 2) % M; }  void returm() {     std::string s;std::cin >> s;     int n = s.size();      s = ' ' + s;      std::vector<int> pre(n + 2), suf(n + 2), sum(n + 2);      for(int i = 1;i <= n;i ++) {         sum[i] = sum[i - 1] + (s[i] == '(' ? 1 : -1);     }      for(int i = 1;i <= n;i ++) {         pre[i] = pre[i - 1] + (s[i] == '(');     }      for(int i = n;i >= 1;i --) {         suf[i] = suf[i + 1] + (s[i] == ')');     }      std::vector<int> p(n + 1);      p[0] = 1;     for(int i = 1;i <= n;i ++) {         p[i] = p[i - 1] * 2 % M;     }      int ans = 0;     for(int i = 1, j = 1;i <= n;i ++) {         j = std::max(j, i);          while(j <= n && sum[j] > 1)j ++;          if(s[i] == '(') {             ans = (ans + p[pre[i - 1]] * p[suf[j + 1]] % M) % M;         }     }      ans = (ans + p[n / 2]) % M;     ans = ans * inv(p[n]) % M;      std::cout << (ans % M + M) % M << 'n'; }  signed main() {     std::ios::sync_with_stdio(false);     std::cin.tie(nullptr);     std::cout.tie(nullptr);      int t = 1;//std::cin >> t;     while(t --) {         returm();     } } 

杭电多校2补题

杭电多校3补题

发表评论

评论已关闭。

相关文章