2025-07-27 模拟赛总结 😥

预期:\(100+100+100+100=400\)
实际:\(100+15+100+100=315\)
排名:\(rk25/127\)

比赛链接:http://oj.daimayuan.top/contest/367

A - 和除或:

题意:

给定长度为 \(n\) 的序列 \(a\),保证每个 \(a_i\) 至多拥有四个二进制位,你需要求出 \(\displaystyle\sum_{i=1}^n\sum_{j=1}^n\lceil\frac{a_i+a_j}{a_i\operatorname{or}a_j}\rceil\) 的值。

思路:

由于 \(x+y=(x\operatorname{and}y)+(x\operatorname{or}y)\),将其带入得到当 \(a_i\operatorname{and}a_j=0\)\(\lceil\frac{a_i+a_j}{a_i\operatorname{or}a_j}\rceil=1\),否则 \(\lceil\frac{a_i+a_j}{a_i\operatorname{or}a_j}\rceil=2\)

现在题目转化为求序列中有多少对 \(a_i\operatorname{and}a_j\neq 0\)

由于每个 \(a_i\) 至多拥有四个二进制位,所以我们可以容斥,加上至少 \(1\) 个二进制位相同的,减去 \(2\) 个二进制位相同的……,这样就可以了。

但是有个小细节,你不能直接开数组,因为有多组询问,你需要开 hash 表来储存。

代码:

#include <bits/stdc++.h>
#include <bits/extc++.h>

using namespace std;
using namespace __gnu_pbds;

const int kMaxN = 2e5 + 5, kL = 32;

int T, n, a[kMaxN];
long long ans;
vector<int> v;

gp_hash_table<int, int> c1;
gp_hash_table<int, gp_hash_table<int, int>> c2;
gp_hash_table<int, gp_hash_table<int, gp_hash_table<int, int>>> c3;
gp_hash_table<int, gp_hash_table<int, gp_hash_table<int, gp_hash_table<int, int>>>> c4;

int main() {
  ios::sync_with_stdio(0), cin.tie(0);
  for (cin >> T; T; T--, ans = 0) {
    cin >> n;
    for (int i = 1; i <= n; i++, v.clear()) {
      cin >> a[i];
      for (int j = 0; j < kL; j++) {
        if (a[i] & (1 << j)) {
          v.push_back(j);
        }
      }
      for (int j : v) {
        c1[j]++;
      }
      for (int j : v) for (int k : v) {
        if (j < k) c2[j][k]++;
      }
      for (int j : v) for (int k : v) for (int l : v) {
        if (j < k && k < l) c3[j][k][l]++;
      }
      for (int j : v) for (int k : v) for (int l : v) for (int m : v) {
        if (j < k && k < l && l < m) c4[j][k][l][m]++;
      }
    }
    for (auto i : c1) {
      ans += 1LL * i.second * (i.second - 1) / 2;
    }
    for (auto i : c2) {
      for (auto j : i.second) {
        ans -= 1LL * j.second * (j.second - 1) / 2;
      }
    }
    for (auto i : c3) {
      for (auto j : i.second) {
        for (auto k : j.second) {
          ans += 1LL * k.second * (k.second - 1) / 2;
        }
      }
    }
    for (auto i : c4) {
      for (auto j : i.second) {
        for (auto k : j.second) {
          for (auto l : k.second) {
            ans -= 1LL * l.second * (l.second - 1) / 2;
          }
        }
      }
    }
    cout << ans + 1LL * n * (n - 1) / 2 << '\n';
    c1.clear(), c2.clear(), c3.clear(), c4.clear();
  }
  return 0;
}

B - 礼物:

题意:

定义一个只包含 A/B 的字符串 \(s\)的,当且仅当 \(s\) 中不存在长度为 \(k\) 的连续 AB

你至多进行一次操作,每次操作你可以将字符串的一个子串左右翻转。问你是否可行。

思路:

首先排除掉长度为 \(2k-1\) 的全 A/B 子串,这种情况肯定不行。

其次排除掉有 \(2\) 个以上的极长全 A/B 子串,这种情况也肯定不行。

若只有 \(0\) 个以上的极长全 A/B 子串,这种情况可以不操作,肯定可行。

所以只需要考虑 \(1,2\) 个极长全 A/B 子串,先考虑 \(2\) 个的情况,结论很显然,若这两个极长子串颜色相同,那么肯定不可行,否则可行。

最后考虑 \(1\) 个极长全 A/B 子串 \(p\),我们找出一个最短的极长全 A/B 子串 \(q\)(要和其颜色相同,且这一段的长度肯定小于 \(k\)),我们将 \(p\) 的中间一段到 \(q\) 的末尾翻转,判断一下是否可行就可以了。

代码:

#include <bits/stdc++.h>

using namespace std;

const int kMaxN = 2e5 + 5;

int T, n, k;
string s;

void Solve() {
  cin >> n >> k >> s, s = ' ' + s;
  int cnt = 0, l = 0, r = 0;
  string t = "";
  for (int i = 1, c = 0; i <= n; i++) {
    if (s[i] == s[i - 1]) {
      c++;
    } else {
      c = 1;
    }
    if (c >= 2 * k - 1)  {
      cout << "NO\n";
      return;
    }
    if (c == k) {
      t += s[i], r = i;
    }
  }
  if (t.size() > 2) {
    cout << "NO\n";
  } else if (t.size() == 2) {
    cout << (t[0] == t[1] ? "NO\n" : "YES\n");
  } else if (t.size() == 1) {
    if (s[1] != t[0] || s[n] != t[0]) {
      cout << "YES\n";
      return;
    }
    l = r;
    for (; l > 1 && s[l - 1] == t[0]; l--) {
    }
    for (; r < n && s[r + 1] == t[0]; r++) {
    }
    int p = r - l + 1 - k + 1;
    for (int i = 1, c = 0; i <= n; i++) {
      if (s[i] == t[0]) {
        c++;
      }
      if (s[i] != t[0] || i == n) {
        if (c < k - p) {
          cout << "YES\n";
          return;
        }
        c = 0;
      }
    }
    cout << "NO\n";
  } else {
    cout << "YES\n";
  }
}

int main() {
  ios::sync_with_stdio(0), cin.tie(0);
  for (cin >> T; T; T--) {
    Solve();
  }
  return 0;
}

反思:

这道题我的思路特别清晰,一步一步简化条件,相较之前有所进步。只不过最后一种情况,忘记判断 i==n 的情况了,犯了个小错误(85pts 没了啊啊啊)。这道题我特意对拍了,但是暴力写挂了(也是这个问题),导致最后没怎么检查,下次要细心一点(唯一一次挂的题目是在场上对拍了的。是对拍的问题吗?

C - 连续段的价值:

题意:

定义一个字符串的 \(f_i\) 表示全为 \(i\) 的最长子串长度,若没有这个字符就为 \(0\)

输入给定一个字符串 \(s\),由前 \(k\) 个小写字母和 ? 组成,你可以将每个 ? 替换成任意一个前 \(k\) 个小写字母,你需要最大化 \(\min f_i\)

数据范围:\(|s|\le 2\times 10^5,1\le k\le 17\)

思路:

看到 \(17\) 想到状压,最大化最小值想到二分。

二分答案 \(d\),那么每个字符都需要连续出现至少 \(d\) 次,我们考虑每个字符连续出现了 \(d\) 次的位置,我们发现这个位置关系是一个全排列,换句话说,我们确定每个字符出现连续 \(d\) 次的顺序,我们可以贪心的往前选,这样就可以做到 \(O(k!\times\operatorname{poly}(n))\) 的时间复杂度了。

当然如果你预处理往前选的过程,那么你可以做到 \(O(k!+kn)\) 的时间复杂度(这已经很接近正解了)。

经典地,全排列可以用 \(O(k^22^k)\) 的状压 dp 解决。我们设 \(f_{i,s}\) 表示现在选到 \(i\) 字符,已选字符集合为 \(s\),所用到的最短前缀长度。若从 \(f_{j,s/\{i\}}\) 转移过来,设从 \(f_{j,s/\{i\}}+1\) 往后连续出现了 \(d\)\(i\) 的位置最靠前为 \(p\),那么 \(f_{i,s}\gets p\),这样做就可以了。这个 \(p\) 可以 \(O(nk)\) 预处理算出。但是 \(O(k^22^k)\) 可能会被卡常。

我们可以将空间和时间进一步的优化,因为每次转移只与 \(f_{j,s/\{i\}}\) 的大小有关,与 \(j\) 无关,所以我们可以将 \(f\) 第一维删去,同样是正确的。

代码:

#include <bits/stdc++.h>

using namespace std;

const int kMaxN = 2e5 + 5, kMaxK = 17;

int n, k, L, R, M, f[1 << kMaxK], nex[kMaxN][kMaxK];
string s;

bool C(int d) {
  for (int c = 0; c < k; c++) {
    nex[n + 1][c] = 1e9;
    for (int i = n, len = 0; i; i--) {
      if (s[i] == c + 'a' || s[i] == '?') {
        len++;
      } else {
        len = 0;
      }
      nex[i][c] = len >= d ? i + d - 1 : nex[i + 1][c];
    }
  }
  fill(f, f + (1 << k), 1e9);
  f[0] = 0;
  for (int i = 0; i < 1 << k; i++) {
    for (int c = 0; c < k; c++) {
      if (i >> c & 1) continue;
      if (f[i] + 1 <= n) f[i | (1 << c)] = min(f[i | (1 << c)], nex[f[i] + 1][c]);
    }
  }
  return f[(1 << k) - 1] <= n;
}

int main() {
  ios::sync_with_stdio(0), cin.tie(0);
  cin >> n >> k >> s, s = ' ' + s;
  L = 0, R = n, M = L + R + 1 >> 1;
  for (; L < R; M = L + R + 1 >> 1) {
    C(M) ? L = M : R = M - 1;
  }
  cout << L;
  return 0;
}

D - 送外卖:

题意:

题意难以概括,放个原题面链接吧。http://oj.daimayuan.top/contest/367/problem/3246

思路:

先考虑暴力,修改就暴力修改数组,查询就设一个当前时间 \(t\) 初始为 \(x_l\),到达 \(i\) 点,就判断 \(t\) 是否大于 \(y_l\),接着让 \(t\gets\max(t+w[i],x[i+1])\)。这样做时间复杂度为 \(O(nq)\)

看到这个形式,哇典中典,直接 ddp(也不完全是 ddp,准确来说是广义矩阵乘法)就没了,唯一需要注意的是,矩阵里还要存一个 \(\max\{t-y_i\}\),最后得到结果后就相当于要判断这个数小于等于 \(0\)。这样做时间复杂度 \(O(q\log n)\),只不过常数有点大,可能有 50 多。

代码:

#include <bits/stdc++.h>
#define mid (l + r >> 1)

using namespace std;

const int kMaxN = 1e6 + 5;

int T, n, q, o, p, l, r, x[kMaxN], y[kMaxN], w[kMaxN];

struct M {
  int n, m;
  long long a[3][3];

  M() { memset(a, 0xc0, sizeof(a)); }
  M(int h, int w) { n = h, m = w, memset(a, 0xc0, sizeof(a)); }

  long long* operator[](int x) { return a[x]; }

  M operator*(M y) const {
    M r(n, y.m);
    long long x;
    for (int i = 0; i < n; i++) {
      for (int k = 0; k < m; k++) {
        if ((x = a[i][k]) < -1e18) continue;
        for (int j = 0; j < y.m; j++) {
          r[i][j] = max(r[i][j], x + y[k][j]);
        }
      }
    }
    return r;
  }
} t[kMaxN << 2], F(1, 3);

M Get(int i) {
  M r(3, 3);
  r[0][0] = w[i], r[0][2] = -y[i], r[1][0] = x[i + 1], r[1][1] = r[2][2] = 0;
  return r;
}

void PushUp(int u) {
  if (!t[u << 1].n) t[u] = t[u << 1 | 1];
  else if (!t[u << 1 | 1].n) t[u] = t[u << 1];
  else t[u] = t[u << 1] * t[u << 1 | 1];
}

void Build(int u, int l, int r) {
  if (l == r) {
    t[u] = Get(l);
    return;
  }
  Build(u << 1, l, mid), Build(u << 1 | 1, mid + 1, r);
  PushUp(u);
}

void Update(int u, int l, int r, int p) {
  if (l == r) {
    t[u] = Get(p);
    return;
  }
  if (p <= mid) Update(u << 1, l, mid, p);
  else Update(u << 1 | 1, mid + 1, r, p);
  PushUp(u);
}

M Query(int u, int l, int r, int L, int R) {
  if (L <= l && r <= R) return t[u];
  if (R <= mid) return Query(u << 1, l, mid, L, R);
  if (L > mid) return Query(u << 1 | 1, mid + 1, r, L, R);
  return Query(u << 1, l, mid, L, R) * Query(u << 1 | 1, mid + 1, r, L, R);
}

int main() {
  ios::sync_with_stdio(0), cin.tie(0);
  for (cin >> T; T; T--) {
    cin >> n;
    for (int i = 1; i <= n; i++) {
      cin >> x[i];
    }
    for (int i = 1; i <= n; i++) {
      cin >> y[i];
    }
    for (int i = 1; i < n; i++) {
      cin >> w[i];
    }
    Build(1, 1, n);
    for (cin >> q; q; q--) {
      cin >> o;
      if (o == 0) {
        cin >> l >> r;
        F[0][0] = x[l], F[0][1] = 0, F[0][2] = -4e18;
        F = F * Query(1, 1, n, l, r);
        cout << (F[0][2] <= 0 ? "Yes\n" : "No\n");
      } else if (o == 1) {
        cin >> p >> r;
        w[p] = r, Update(1, 1, n, p);
      } else {
        cin >> p >> l >> r;
        x[p] = l, y[p] = r;
        if (p != 1) Update(1, 1, n, p - 1);
        Update(1, 1, n, p);
      }
    }
  }
  return 0;
}

总结:

时间分配:\(40+40+40+40=160\)

每一题都是正常速度吧,只不过写完后就在卡 T4 的常数和摆烂,没有管 B 题,以为过拍就能过,没想到暴力写挂了(

这一次的题目感觉都很顺,推一推就把正解推出来了,完全没有卡壳的地方。(感觉模拟赛越出越简单了。

模拟赛还是没有正赛那样的紧张感,如果是正赛我肯定会检查检查每一道题,模拟赛就没有了,以后要把每一场模拟赛都当成正赛打。有多余的时间不要放飞自我,细心检查代码,如果剩下的 \(80min\) 我检查一下有可能就能 AK 了呢。但是考都考完了就不要给自己加分了。

(其实本来有 26 人可以 AK 的,到了最后只有 14 人了,都跟我一样挂 B 了。

posted @ 2025-07-27 14:33  liruixiong0101  阅读(40)  评论(0)    收藏  举报