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\)
令 \(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;
}
我也不知道为什么 \(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;
}
\(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;
}
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;
}

浙公网安备 33010602011771号