AtCoder Beginner Contest 455 题解
AtCoder Beginner Contest 455 题解
| 项目 | 内容 |
|---|---|
| 比赛时间 | 2026-04-25 |
| 比赛链接 | https://atcoder.jp/contests/abc455 |
| 题目数量 | A–G 共 7 题 |
比赛概览
本场难度梯度非常清晰:A、B 是纯签到题(一个条件判断、一个四重循环暴力枚举);C 是"贪心 + 计数"(按 取前 大);D 是用 up/down 数组在 内维护"整叠移动"的经典链表题,值得细品;E 是"容斥 + 前缀差分"的字符串计数;F 的关键观察是合并史莱姆的总代价与合并顺序无关,配合懒标记线段树维护区间和与区间平方和;G 是压轴的难题,需要滑动窗口 + 随机哈希,在 内分别统计两类"平衡子数组"。其中 D、F、G 三题较有区分度,是值得重点掌握的考点。
A - 455
题目链接:https://atcoder.jp/contests/abc455/tasks/abc455_a
题目大意
给定三个整数 ()。若 且 ,输出 Yes,否则输出 No。
思路
没有任何技巧,直接按题意把两个条件翻译成代码即可:
- 条件一:;
- 条件二:;
两者同时满足(逻辑与)时输出 Yes,否则输出 No。
易错点:注意是比较 与 是否相等,而不是 与 ;条件一是"不相等",条件二是"相等",方向别写反。示例 3 中 因为 而不满足条件一,输出 No。
参考代码
#include <bits/stdc++.h>
using namespace std;
int main() {
int A, B, C;
cin >> A >> B >> C;
if (A != B && B == C) cout << "Yes\n";
else cout << "No\n";
return 0;
}官方题解还给出了等价的 Python 写法:
a, b, c = map(int, input().split())
print("Yes" if a != b and b == c else "No")复杂度分析
- 时间复杂度:
- 空间复杂度:
B - Spiral Galaxy
题目链接:https://atcoder.jp/contests/abc455/tasks/abc455_b
题目大意
有一个 行 列的网格,. 表示白格、# 表示黑格。称一个矩形区域是"点对称染色"的,如果区域内任意格子 与它关于矩形中心对称的格子 颜色相同。求满足条件的矩形区域个数()。
思路
意味着矩形的数量至多
直接暴力即可:用四重循环枚举矩形的上边 、下边 、左边 、右边 ,再检查区域内所有格子是否满足点对称条件。
点对称的含义:矩形中心位于 ,格子 的对称格为
条件要求 对所有区域内 成立。注意到 的对称格恰好是 ,所以即便把所有格子的配对重复检查两次也不影响正确性;实现时可以对区域内每个格子直接比较,一旦发现不匹配就判定失败、提前退出。
为什么正确:四重循环穷举了所有可能的矩形(每个 唯一对应一个矩形),对每个矩形都完整地验证了点对称条件,因此计数不重不漏。
复杂度:最坏情况下检查的总格点数等于
完全可行。实现时用字符比较即可,注意下标从 0 开始。
参考代码
#include <bits/stdc++.h>
using namespace std;
int main() {
int H, W;
cin >> H >> W;
vector<string> S(H);
for (int i = 0; i < H; i++) cin >> S[i];
long long ans = 0;
for (int h1 = 0; h1 < H; h1++)
for (int h2 = h1; h2 < H; h2++)
for (int w1 = 0; w1 < W; w1++)
for (int w2 = w1; w2 < W; w2++) {
bool ok = true;
for (int i = h1; i <= h2 && ok; i++)
for (int j = w1; j <= w2 && ok; j++) {
int ii = h1 + h2 - i, jj = w1 + w2 - j;
if (S[i][j] != S[ii][jj]) ok = false;
}
if (ok) ans++;
}
cout << ans << "\n";
return 0;
}复杂度分析
- 时间复杂度:( 时实际检查约 次)
- 空间复杂度:
C - Vanish
题目链接:https://atcoder.jp/contests/abc455/tasks/abc455_c
题目大意
给定长度为 的整数序列 与操作次数 。一次操作:选定整数 ,把 中所有等于 的元素变为 。执行恰好 次操作后,求序列元素和的最小可能值。,。
思路
关键观察:一次选择 的操作,效果等价于"把数值为 的元素从序列中全部删掉"。设 为 在 中出现的次数,则对 做一次操作能让总和减少
由于每次操作必须选定一个 ,而不同 之间互不影响(删除 不会改变其它数值的出现次数),所以为了让最终和最小,应当把 个"收益" 中最大的 个对应数值各操作一次。
为什么正确:
- 对同一个 操作两次是无意义的(第一次已把它全部清零,第二次无任何效果),所以最优解中每个 至多被选一次;
- 收益之间相互独立,总和减少量等于被选数值的 之和,因此等价于从集合 中选 个最大的;
- 若不同数值的个数不足 ,多余的次数可以选一个"从未出现过的 "来完成操作(此时没有任何元素变为 0,序列不变),不影响结果。
因此答案 = 原始总和 − 前 大的 之和。
实现: 可达 ,不能用普通数组计数,用 map(或对排序后的 做游程压缩)统计每个数值的出现次数,得到向量 后降序排序、减去前 项即可。注意答案用 long long。
参考代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int N, K;
cin >> N >> K;
map<ll, ll> cnt;
ll total = 0;
for (int i = 0; i < N; i++) {
ll a;
cin >> a;
cnt[a]++;
total += a;
}
vector<ll> gain;
for (auto& [x, c] : cnt) gain.push_back(x * c);
sort(gain.rbegin(), gain.rend());
for (int i = 0; i < K && i < (int)gain.size(); i++) total -= gain[i];
cout << total << "\n";
return 0;
}复杂度分析
- 时间复杂度:(
map计数 + 排序) - 空间复杂度:
D - Card Pile Query
题目链接:https://atcoder.jp/contests/abc455/tasks/abc455_d
题目大意
有 张牌和 叠牌,初始时第 叠只有牌 。依次执行 个操作:把牌 以及它上面的所有牌(保持相对顺序)整叠移动到牌 的上方。保证操作前 与 在不同叠中,且 在某叠的顶部。问全部操作结束后每叠各有多少张牌。。
思路
若朴素维护"每叠的牌序列",一次移动可能搬动一叠里的很多牌,总复杂度会退化成 ,不可行。
关键观察:我们只在结束时需要每叠的张数,不需要中间过程。而"把 顶上的一整块挪到 上"本质上是把一个栈拆开、接到另一个栈上——用两个数组精确记录"每张牌上下相邻是谁"即可在 内完成。
定义两个数组(下标 ):
up[i]:牌 上方的牌(没有则为 );down[i]:牌 下方的牌(没有则为 )。
为了让"某叠空了"这个状态也能自然表达,给第 叠底部放一张永远不会被移动的哨兵牌 。初始化时:
即哨兵在底、牌 在哨兵上方。
一次操作 中,设 ( 下方的牌)。把以 为底的整块从 上方拆下、接到 上方,只需三步:
上方那一块的内部关系完全不变,无需任何修改。
为什么正确:
- 操作前 必在某叠顶部,故 ,把 接到 上方合法;
- 是 原来的下方邻牌,拆走后 上方不再有牌,故 ;
- 哨兵永不参与移动:当 是某叠的底牌时 就是该叠的哨兵,更新后哨兵的
up变为 ,表示该叠为空; - 由于每张牌的
up/down只与它的直接上下邻牌有关, 次修改就完整刻画了新状态。
结束时,从第 叠的哨兵 出发沿 up 指针向上走,走过的牌数就是该叠的牌数。每条链(每叠)恰好被遍历一次,总代价 。
参考代码
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int N, Q;
cin >> N >> Q;
vector<int> up(2 * N, -1), down(2 * N, -1);
for (int i = 0; i < N; i++) {
up[N + i] = i; // 第 i 叠的哨兵 N+i 上方是牌 i
down[i] = N + i; // 牌 i 下方是它所在叠的哨兵
}
while (Q--) {
int c, p;
cin >> c >> p;
c--; p--;
int d = down[c];
down[c] = p; // C 接到 P 上方
up[p] = c;
up[d] = -1; // D 上方没有牌了
}
for (int i = 0; i < N; i++) {
int x = N + i, ans = 0;
while (up[x] != -1) {
x = up[x];
ans++;
}
cout << ans << (i + 1 == N ? "\n" : " ");
}
return 0;
}复杂度分析
- 时间复杂度:(每个操作 ,结束时每条牌链遍历一次)
- 空间复杂度:
E - Unbalanced ABC Substrings
题目链接:https://atcoder.jp/contests/abc455/tasks/abc455_e
题目大意
给定长度 、只含 A、B、C 三种字符的字符串 ()。求有多少个非空子串满足:A、B、C 在其中的出现次数两两不同。位置不同即视为不同子串。
思路
设子串中 A、B、C 的出现次数分别为 (稍后会看到上下文区分)。"两两不同"即 、、 三个条件同时成立。直接用容斥原理处理"不等"比较麻烦,转而对相等事件做容斥。
设 表示满足"出现次数 "的子串个数,其余同理, 表示三者全相等的子串个数。全部非空子串数为 。对"三个相等条件全部不成立"作容斥:
其中第二项到第四项来自: 等三组两两交,均等于 ,而三交集也是 。化简得:
如何统计 :设 分别为前 个字符中 A、B 的个数(),则子串 (第 到第 个字符)中 A、B 的出现次数分别为 、。于是
所以只需对每个前缀下标 计算差值 ,然后统计"有多少对 的 ":若某个值出现 次,则贡献 。、 同理。
:三个相等等价于两个独立条件同时成立,例如
等价于二元组 。对每个 计算二元组,用哈希表统计每种二元组出现次数 ,累加 即可。
复杂度:前缀差值范围在 内,单值用 unordered_map、二元组用 map(或自定义哈希的 unordered_map)均可,总复杂度 或 。
参考代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
// 统计前缀差数组中相同值出现的对数 sum C(c,2)
ll countEqual(const vector<ll>& key) {
unordered_map<ll, ll> freq;
for (ll x : key) freq[x]++;
ll res = 0;
for (auto& [k, c] : freq) res += c * (c - 1) / 2;
return res;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int N;
string S;
cin >> N >> S;
ll total = (ll)N * (N + 1) / 2;
vector<int> pA(N + 1, 0), pB(N + 1, 0), pC(N + 1, 0);
for (int i = 0; i < N; i++) {
pA[i + 1] = pA[i] + (S[i] == 'A');
pB[i + 1] = pB[i] + (S[i] == 'B');
pC[i + 1] = pC[i] + (S[i] == 'C');
}
vector<ll> keyAB(N + 1), keyAC(N + 1), keyBC(N + 1);
for (int i = 0; i <= N; i++) {
keyAB[i] = (ll)(pA[i] - pB[i]);
keyAC[i] = (ll)(pA[i] - pC[i]);
keyBC[i] = (ll)(pB[i] - pC[i]);
}
ll fAB = countEqual(keyAB);
ll fAC = countEqual(keyAC);
ll fBC = countEqual(keyBC);
map<pair<ll, ll>, ll> freq;
for (int i = 0; i <= N; i++) freq[{keyAB[i], keyAC[i]}]++;
ll fABC = 0;
for (auto& [pr, c] : freq) fABC += c * (c - 1) / 2;
ll ans = total - fAB - fAC - fBC + 2 * fABC;
cout << ans << "\n";
return 0;
}复杂度分析
- 时间复杂度:(二元组用
map;若自定义哈希可到期望 ) - 空间复杂度:
F - Merge Slimes 2
题目链接:https://atcoder.jp/contests/abc455/tasks/abc455_f
题目大意
有一个长度 、初始全 0 的序列 。依次处理 个查询,每个查询给定 :
- 先把 加到 上;
- 记 ,。有 只重量分别为 的史莱姆,每次合并两只重量为 的史莱姆,代价为 ,新史莱姆重量 。求合并 次的最小总代价(对 取模)。
,,且对 的修改会保留到后续查询。
思路
1. 关键观察:总代价与合并顺序无关
这是本题的核心。把每只史莱姆想象成 个人()。一次合并两只史莱姆的代价 ,恰好等于"两群人之间的配对数量"。
考虑任意一对人 :如果 最初属于同一只史莱姆,那么它们在全部 次合并中永远不会成为"不同群之间的配对被计入"的对象;如果 最初属于不同的史莱姆,那么它们各自所在的群在某一次合并中恰好第一次合到一起,就在那一刻被计入一次,之后它们一直同群,不再被计入。因此:
(总对数减去同群的对数再除以 2,即 。)由于每一对人被计入的次数与合并顺序无关,总代价与合并顺序无关,所以"最小总代价"就是上式本身。
2. 问题转化为区间求和
于是每个查询的答案只取决于两样东西:
需要支持两种操作:
- 区间整体加一个值 ;
- 查询区间和 与区间平方和 。
用懒标记线段树即可。对每个节点维护 (区间长度)、、。对整段加 时:
因为 会随查询累加变得很大(可达 ),但所有量都只在模 下使用,且答案本身是 的整系数多项式,所以全程在模意义下维护 即可,除以 2 用逆元 。
实现细节:push 时按左右子区间的长度分别应用懒标记;a^2*len 与 2a*sum 用 long long 计算后取模(中间值不超过约 ,在 64 位范围内)。
参考代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll MOD = 998244353;
struct SegTree {
int n;
vector<ll> sum, sumsq, lazy; // 均按模 MOD 维护
SegTree(int sz) {
n = 1;
while (n < sz) n <<= 1;
sum.assign(2 * n, 0);
sumsq.assign(2 * n, 0);
lazy.assign(2 * n, 0);
}
void apply(int node, int len, ll a) {
a %= MOD;
sumsq[node] = (sumsq[node] + 2 * a % MOD * sum[node] + a * a % MOD * len) % MOD;
sum[node] = (sum[node] + a * len) % MOD;
lazy[node] = (lazy[node] + a) % MOD;
}
void push(int node, int llen, int rlen) {
if (lazy[node]) {
apply(node * 2, llen, lazy[node]);
apply(node * 2 + 1, rlen, lazy[node]);
lazy[node] = 0;
}
}
void range_add(int node, int nl, int nr, int ql, int qr, ll a) {
if (qr < nl || nr < ql) return;
if (ql <= nl && nr <= qr) { apply(node, nr - nl + 1, a); return; }
int mid = (nl + nr) / 2;
push(node, mid - nl + 1, nr - mid);
range_add(node * 2, nl, mid, ql, qr, a);
range_add(node * 2 + 1, mid + 1, nr, ql, qr, a);
sum[node] = (sum[node * 2] + sum[node * 2 + 1]) % MOD;
sumsq[node] = (sumsq[node * 2] + sumsq[node * 2 + 1]) % MOD;
}
pair<ll, ll> query(int node, int nl, int nr, int ql, int qr) {
if (qr < nl || nr < ql) return {0, 0};
if (ql <= nl && nr <= qr) return {sum[node], sumsq[node]};
int mid = (nl + nr) / 2;
push(node, mid - nl + 1, nr - mid);
auto L = query(node * 2, nl, mid, ql, qr);
auto R = query(node * 2 + 1, mid + 1, nr, ql, qr);
return {(L.first + R.first) % MOD, (L.second + R.second) % MOD};
}
};
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int N, Q;
cin >> N >> Q;
SegTree st(N);
const ll inv2 = (MOD + 1) / 2; // 2 的模逆元
while (Q--) {
int l, r;
ll a;
cin >> l >> r >> a;
l--; r--;
st.range_add(1, 0, st.n - 1, l, r, a % MOD);
auto [S, Q2] = st.query(1, 0, st.n - 1, l, r);
ll ans = ((S * S - Q2) % MOD + MOD) % MOD; // 注意取非负
ans = ans * inv2 % MOD;
cout << ans << "\n";
}
return 0;
}复杂度分析
- 时间复杂度:
- 空间复杂度:
G - Balanced Subarrays
题目链接:https://atcoder.jp/contests/abc455/tasks/abc455_g
题目大意
给定长度为 的正整数序列 ()。一个非空连续子数组称为平衡的,如果其中每个出现过的整数出现的次数都相同。对每个 (),求:
- :每个出现过的整数恰好出现 次的平衡子数组个数;
- :恰好包含 种不同整数的平衡子数组个数。
共 组测试,。位置不同即视为不同子数组。
思路
两类计数相互独立,分别处理。为叙述方便,固定一个 。
第一部分:每个出现值恰好出现 次()
条件转化。子数组 (第 到第 个位置)中每个出现值恰好出现 次,等价于:
- 每个值的出现次数 ;
- 每个值的出现次数 。
(若出现次数 满足 且 ,则只能 。)
条件 1 用滑动窗口。固定右端 ,维护指针 使得窗口 中每个值的出现次数都不超过 ,且 是最小的这样的左端。于是所有满足条件 1 的左端 恰为 。每增加一个 ,只有新加入的 可能超限,用它驱动 while 循环右移 ,总代价 。
条件 2 用随机哈希。选取一个大素数模数 (如 )。对每个值 ,生成 个随机数 ,要求
把 的第 次出现( 基序号)赋值为 。定义前缀哈希
其中 是位置 上赋的值。则子数组 的哈希为 。
看单个值 的贡献:它在子数组中出现了 次,对应的是一段长度为 的连续序号区间,其贡献是循环序列 上连续 项之和。若 ,这段恰好完整覆盖每个余数 次,贡献为 ;若 ,它覆盖的是余数环上的一段"真子区间",贡献是一个随机值,几乎不可能为 0。因此
计数。对每个 ,在 中数出满足 的个数。这可以通过一边扫描一边维护 H_l 的频率表完成:右端前进时把 加入频率表, 右移时把 从表中删去,然后查询 freq[H_r]。每个下标至多入表、出表各一次,总 。
关于生成 的实现:若 出现次数 ,生成全部 个值(最后一个取负和);若 ,则被用到的余数只有 (此时连续区间不会环绕),直接生成 个自由随机数即可。对每个 值总共生成 个随机数。
第二部分:恰好包含 种不同值()
左端区间与唯一集合。固定右端 ,令 为 中不同值的个数。 关于 单调不增(左端右移只会删元素,不同值个数不会增加),所以满足 的 构成一个连续区间 。进一步,由于左端右移只会让"集合"减少而不会引入新值,区间内所有 对应的不同值集合完全相同。称这个唯一集合的哈希为 。
给每个整数值 分配随机数 ,令前缀和
子数组 长度为 ,若它恰好含 种不同值(集合哈希 )且平衡,设每种值都出现 次,则 ,且
反之亦然(哈希冲突概率可忽略)。把 代入并移项:
即对 ,需满足 。所以 等于:对每个 ,在 中数出 的个数。
频率表的维护。当 递增时, 的左右端点都单调不降。若集合(哈希 )不变,则 是同一条线性函数,频率表可增量维护:删去 、加入 。若集合改变,需要按新 重建频率表。关键性质:集合改变时必有 ——否则新旧区间在 上有公共部分,公共部分对应的子数组同时属于两个集合,只能同集合,矛盾。因此重建涉及的左端下标全部大于旧区间右端点,每个下标在整个过程中至多入表一次,重建总代价 。
实现要点:用两个双指针分别维护 (最小左端,保持"不同值个数 ")与 (最大左端,保持"不同值个数 ");集合哈希随 窗口的进出实时维护。若 大于整个序列的不同值个数,答案为 0。
综合复杂度:对每个 两类统计各 次哈希表操作,共 轮,故期望 (最坏 ,取决于哈希表实现)。
参考代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll MOD = 998244353;
ll rnd64() {
static mt19937_64 rng(chrono::steady_clock::now().time_since_epoch().count());
return rng() % MOD;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin >> T;
while (T--) {
int N, K;
cin >> N >> K;
vector<int> A(N);
for (int i = 0; i < N; i++) cin >> A[i];
vector<int> B(K);
for (int i = 0; i < K; i++) cin >> B[i];
// 每个值的出现次数,以及每个位置在该值内的出现序号(0 基)
vector<int> cntv(N + 1, 0);
for (int x : A) cntv[x]++;
vector<int> rank(N);
{
vector<int> cur(N + 1, 0);
for (int i = 0; i < N; i++) rank[i] = cur[A[i]]++;
}
// 每个整数值一个随机"集合哈希"
vector<ll> R(N + 1);
for (int v = 1; v <= N; v++) R[v] = rnd64();
vector<ll> S(N + 1, 0);
for (int i = 0; i < N; i++) S[i + 1] = (S[i] + R[A[i]]) % MOD;
for (int k = 0; k < K; k++) {
int Bk = B[k];
ll ans1 = 0, ans2 = 0;
// ---------- 第一部分:每个出现值恰好出现 Bk 次 ----------
{
// 每个值 v 的循环随机数 w[v][0..Bk-1],总和为 0 (mod MOD)
unordered_map<int, vector<ll>> w;
for (int v = 1; v <= N; v++) {
if (cntv[v] == 0) continue;
if (cntv[v] >= Bk) {
auto& vec = w[v];
vec.resize(Bk);
ll s = 0;
for (int j = 0; j < Bk - 1; j++) {
vec[j] = rnd64();
s += vec[j];
if (s >= MOD) s -= MOD;
}
vec[Bk - 1] = (MOD - s) % MOD;
} else {
auto& vec = w[v];
vec.resize(cntv[v]); // 只用到余数 0..c-1,自由随机即可
for (int j = 0; j < cntv[v]; j++) vec[j] = rnd64();
}
}
vector<ll> H(N + 1, 0);
for (int i = 0; i < N; i++) {
int v = A[i];
H[i + 1] = (H[i] + w[v][rank[i] % Bk]) % MOD;
}
unordered_map<ll, ll> freq;
vector<int> cnt(N + 1, 0);
int ptr = 0;
freq[H[0]]++;
for (int r = 1; r <= N; r++) {
int v = A[r - 1];
cnt[v]++;
while (cnt[v] > Bk) { // 出现次数不得超过 Bk
int u = A[ptr];
cnt[u]--;
ll h = H[ptr];
if (--freq[h] == 0) freq.erase(h);
ptr++;
}
auto it = freq.find(H[r]); // 数 l in [ptr, r-1] 使 H_l == H_r
if (it != freq.end()) ans1 += it->second;
freq[H[r]]++;
}
}
// ---------- 第二部分:恰好含 Bk 种不同值 ----------
{
vector<int> cntLo(N + 1, 0), cntHi(N + 1, 0);
int lo = 0, hi = 0; // 左端区间 [lo, hi]
int dLo = 0, dHi = 0; // [lo,r-1] 与 [hi,r-1] 的不同值个数
ll setHash = 0; // [lo, r-1] 内不同值的集合哈希
unordered_map<ll, ll> freq; // F_l 的频率表
bool valid = false; // 当前是否存在有效区间
ll curH = -1;
int curL = 0, curU = -1;
auto F = [&](ll Sv, ll Hh, int idx) -> ll {
ll f = (Bk * Sv % MOD - Hh * idx % MOD) % MOD;
if (f < 0) f += MOD;
return f;
};
for (int r = 1; r <= N; r++) {
int v = A[r - 1];
// lo 窗口 [lo, r-1]:不同值个数 <= Bk,lo 最小
if (cntLo[v] == 0) { dLo++; setHash = (setHash + R[v]) % MOD; }
cntLo[v]++;
while (dLo > Bk) {
int u = A[lo];
cntLo[u]--;
if (cntLo[u] == 0) { dLo--; setHash = (setHash - R[u] + MOD) % MOD; }
lo++;
}
// hi 窗口 [hi, r-1]:不同值个数 >= Bk,hi 最大
if (cntHi[v] == 0) dHi++;
cntHi[v]++;
while (true) {
int u = A[hi];
if (dHi - (cntHi[u] == 1 ? 1 : 0) >= Bk) {
cntHi[u]--;
if (cntHi[u] == 0) dHi--;
hi++;
} else break;
}
if (dLo != Bk || dHi < Bk) { // 不存在恰好 Bk 种的左端
valid = false;
continue;
}
if (!valid || setHash != curH) {
// 集合变化:用新哈希重建频率表(新下标均未入过表)
freq.clear();
for (int l = lo; l <= hi; l++) freq[F(S[l], setHash, l)]++;
valid = true;
curH = setHash;
curL = lo;
curU = hi;
} else {
for (int l = curL; l < lo; l++) {
ll f = F(S[l], curH, l);
if (--freq[f] == 0) freq.erase(f);
}
for (int l = curU + 1; l <= hi; l++) freq[F(S[l], curH, l)]++;
curL = lo;
curU = hi;
}
ll fr = F(S[r], setHash, r);
auto it = freq.find(fr);
if (it != freq.end()) ans2 += it->second;
}
}
cout << ans1 << " " << ans2 << "\n";
}
}
return 0;
}复杂度分析
- 时间复杂度:每个测试点期望 (使用哈希表;用平衡树则为 ),其中两类统计各自的每个下标均只入/出表常数次
- 空间复杂度: