2025/7/22 模拟赛总结

\(100+100+20+9=229\),罕见的没有挂分场

赛时记录:

\(3:53:33\) 看完所有题

\(3:45:39\) 写 A 40pts(\(O(n^2)\)

\(3:40:00\) 写完,开始写 70pts(\(O(n^2)+\text{A}\)),\(40+0+0+0=40\)

\(3:22:53\) 写完,\(70+0+0+0=70\)

\(3:18:16\) 写 B 70pts(\(O(n^2m)\)

\(2:40:01\) 写完,\(70+70+0+0=140\)

\(2:39:12\) 写 C 70pts(\(O(n^2)\)

\(2:12:27\) 发现假了,于是想 A 100pts(\(O(n \log n)\)

\(1:13:30\) 写完了,\(100+70+0+0=170\)

\(0:51:11\) 写完 D 9pts(\(\text{A}+\text{B}\)),\(100+70+0+9=179\)

\(0:44:04\) 写完 C 20pts(\(O(n^3)\)),\(100+70+20+9=199\)

\(0:10:21\) 写完 B 正解(\(O(nm\log m)\)),\(100+100+20+9=229\)

#A. LIS

\(f_i\)\(\displaystyle\min_{i<j\le n,a_j>a_i}{j}\),若 \(a_i\) 为后缀最大值则 \(f_i=n+1\)

又令 \(c_i\) 为满足条件的 \(j\) 的数量 \(+1\)

  • \(1\le j<i\),当 \(L=j,R>i\) 时,\(a_i\) 对答案有贡献

\(c_i\) 的递推式很好求。将所有 \(c_i\) 赋值为 \(1\),顺序遍历,计算 \(c_{f_i} := c_{f_i}+c_i\) 即可

此时暴力 \(O(n^2)\)\(f_i\) 就可以拿到 \(40\) 分了,然后考虑 \(f_i\)\(O(n\log n)\) 求法

考虑倍增,令 \(mx_{i,j}\)\(i\sim \min(n,i+2^{j-1})\) 中最大的 \(a_i\),这是容易倍增求出的

类似倍增 LCA 的想法,以 \(cur=i\) 为起点,每次以 \(2^j\) 为单位跳,最后的 \(cur\) 就是 \(f_i\)

答案即为:\(\displaystyle\frac{n(n+1)}{2}+\sum_{i=1}^{n}c_i(n-f_i+1)\)

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

using namespace std;
using LL = long long;

const int kMaxN = 4e5 + 5, kL = 22;

int T, n;
LL a[kMaxN], ans, f[kMaxN], c[kMaxN], mx[kMaxN][kL];

int main() {
  ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
  for (cin >> T; T; T--, ans = 0) {
    cin >> n, fill(f + 1, f + n + 1, n + 1), fill(c + 1, c + n + 1, 1), fill(mx[0], mx[n + 2], 0);
    ans = 1ll * n * (n + 1) / 2, a[n + 1] = -1;
    for (int i = 1; i <= n; i++) {
      cin >> a[i];
    }
    fill(mx[n + 1], mx[n + 2], -1);
    for (int i = n; i; i--) {
      mx[i][0] = a[i];
      for (int j = 1; j < kL; j++) {
        mx[i][j] = max(mx[i][j - 1], mx[min(n, i + (1 << j - 1))][j - 1]);
      }
    }
    for (int i = n; i; i--) {
      int cur = i;
      for (int j = kL - 1; ~j; j--) {
        if (mx[cur][j] <= a[i]) {
          cur = min(n + 1, cur + (1 << j));
        }
      }
      f[i] = cur;
    }
    for (int i = 1; i <= n; i++) {
      c[f[i]] += c[i];
    }
    for (int i = 1; i <= n; i++) {
      ans += c[i] * (n - f[i] + 1);
    }
    cout << ans << '\n';
  }
  return 0;
}

#B. 碰瓷

我也不知道为什么 \(O(n^3)\) 纯暴力给了 \(70\)

枚举 \(b\)\(a\) 中的匹配起始点 \(i\),由于不能在开头插入,需要保证 \(a_i=b_1\)

\(tot\)\(b\) 开头连续等于 \(b_1\) 的数量,\(f_i\)\(a_i\sim a_n\) 的后缀开头连续等于 \(b_1\) 的数量。这些都可以 \(O(n+m)\) 递推求出

因为插入时要保证 \(x\not=b_i\),所以如果 \(f_i>tot\),可以直接判断以 \(i\) 开头长度为 \(m\) 子串是否与 \(b\) 相等即可

因为 \(a_i\le m\),我们可以用 \(m\)set 存下每个数出现的所有位置,从 \(i\) 开始根据当前的 \(b_j\) 进行 upper_bound。若每一位都成功匹配,令 \(b_m\) 匹配的是 \(cur\),由于后面可以根据 \(a_{cur+1}\sim a_n\) 随意在尾部插入,答案需要加上 \(n-cur+1\)

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

using namespace std;
using LL = long long;

const int kMaxN = 2e5 + 5, kC = 105;

LL T, n, a[kMaxN], b[kMaxN], m, ans, F, f[kMaxN], tot;
set<int> s[kC];

int main() {
  ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
  for (cin >> T; T; T--, ans = 0, fill(f, f + n + 2, 0), tot = 0) {
    cin >> n >> m, b[m + 1] = a[n + 1] = -1;
    for (int i = 1; i <= n; i++) {
      cin >> a[i], s[a[i]].insert(i);
    }
    for (int i = 1; i <= m; i++) {
      cin >> b[i];
    }
    for (int i = n; i; i--) {
      f[i] = (a[i] == b[1]) ? (f[i + 1] + 1) : 0;
    }
    for (int i = 1; i <= m; i++) {
      if (b[i] == b[1]) {
        tot++;
      } else {
        break;
      }
    }
    for (int i = 1; i <= n - m + 1; i++) {
      if (a[i] == b[1]) {
        if (f[i] > tot) {
          bool flag = 1;
          for (int j = 1; j <= m; j++) {
            flag &= b[j] == a[i + j - 1];
          }
          ans += flag;
          continue;
        }
        int cur = i, p = 1;
        for (int j = 2; j <= m; j++) {
          if (s[b[j]].upper_bound(cur) == s[b[j]].end()) {
            p = 0;
            break;
          }
          cur = *s[b[j]].upper_bound(cur);
        }
        if (p == 0) {
          continue;
        }
        ans += (n - cur + 1);
      }
    }
    cout << ans << '\n';
    for (int i = 1; i <= m; i++) {
      s[i].clear();
    }
  }
  return 0;
}

#C. 旋律

\(O(n^2)\) 的思路很好想,也不知道赛时是怎么想假的。每次询问暴力修改字符串,求出 \(\text{border}\),从 \(b_n\) 开始,每次令 \(i=b_i,ans:=ans+1\),于是可以在线求出每次询问的答案

将询问离线,倒序向字符串中加入字符,使用两个数组记下每次询问的字符串在原串中的左右端点

将原串哈希,考虑将答案差分。不难发现当 \(\text{border}\) 长度 \(i\) 固定时,是否存在长度为 \(i\)\(\text{border}\) 对下标是有单调性的

于是可以二分最短的不存在长度为 \(i\)\(\text{border}\) 的询问,直接差分即可

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

using namespace std;
using uLL = unsigned long long;

const int kMaxN = 1e6 + 5;
const uLL B = 131;

int n, l[kMaxN], r[kMaxN], curl, curr, op[kMaxN], L, R, ans[kMaxN];
uLL f[kMaxN], p[kMaxN];
string s;
char ch[kMaxN];

uLL Calc(int l, int r) { return f[r] - f[l - 1] * p[r - l + 1]; }
bool Chk(int x, int i) { return Calc(l[x], r[x] - i) == Calc(l[x] + i, r[x]); }

int main() {
  ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
  cin >> n, s.resize(n + 2), curl = 1, curr = n, p[0] = 1;
  for (int i = 1; i <= n; i++) {
    cin >> op[i] >> ch[i];
  }
  for (int i = n; i; i--) {
    l[i] = curl, r[i] = curr, op[i] == 1 ? (s[curl++] = ch[i]) : (s[curr--] = ch[i]);
  }
  for (int i = 1; i <= n; i++) {
    p[i] = p[i - 1] * B, f[i] = B * f[i - 1] + s[i];
  }
  for (int i = 1; i <= n; i++) {
    L = i, R = n + 1;
    for (int mid = L + R >> 1; L + 1 < R; mid = L + R >> 1) {
      Chk(mid, i) ? L = mid : R = mid;
    }
    ans[i]++, ans[R]--;
  }
  for (int i = 1; i <= n; i++) {
    ans[i] += ans[i - 1], cout << ans[i] << '\n';
  }
  return 0;
}

D. 非负

zto guanyf orz

由于 \(a_i=\pm 1\),可以考虑将前缀和画成折线图。根据图易证:\(\forall i\in[l,r],s_{l-1}\le s_i\le s_r\)

所以我们一定可以在 \([l,r]\) 中删去若干个 \(-1\),使子序列长度达到最大,答案为区间长度加区间和减最大子段和

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

using namespace std;

const int kMaxN = 5e5 + 5;

struct P {
  int s, lt, rt, t;
  P operator+(P o) { return (P){s + o.s, max(lt, s + o.lt), max(o.rt, o.s + rt), max({t, o.t, rt + o.lt})}; }
} p[kMaxN << 2];

int n, q, op, l, r, a[kMaxN];

void update(int qx, int x, int l, int r, int k) {
  if (l == r) {
    return p[x] = (P){k, max(k, 0), max(k, 0), max(k, 0)}, void();
  }
  int mid = l + r >> 1;
  qx <= mid && (update(qx, x << 1, l, mid, k), 0), qx > mid && (update(qx, x << 1 | 1, mid + 1, r, k), 0);
  p[x] =p[x << 1] + p[x << 1 | 1];
}
P query(int ql, int qr, int x, int l, int r) {
  if (ql <= l && r <= qr) {
    return p[x];
  }
  int mid = l + r >> 1;
  if (ql <= mid && qr > mid) {
    return query(ql, qr, x << 1, l, mid) + query(ql, qr, x << 1 | 1, mid + 1, r);
  } else if (qr <= mid) {
    return query(ql, qr, x << 1, l, mid);
  } else if (ql > mid) {
    return query(ql, qr, x << 1 | 1, mid + 1, r);
  }
}

int main() {
  ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
  cin >> n >> q;
  for (int i = 1; i <= n; i++) {
    cin >> a[i], update(i, 1, 1, n, a[i]);
  }
  for (; q; q--) {
    cin >> op >> l, op == 2 && (cin >> r);
    if (op == 1) {
      a[l] = -a[l], update(l, 1, 1, n, a[l]);
    } else {
      P ans = query(l, r, 1, 1, n);
      cout << r - l + 1 + ans.s - ans.t << '\n';
    }
  }
  return 0;
}
posted @ 2025-07-23 10:33  BluemoonQwQ  阅读(28)  评论(0)    收藏  举报