2025-7-25 总结
1. 模拟赛 \(2\)
总结:\(\text{STL}\) 掌握不熟悉,小根堆打成大根堆,\(335\to280\)。
A - 子段乘法
1、算法思想维度
问题类型:线段树
做题思路:维护区间的最大子段和与最小子段和,然后枚举中间分割线,答案为最大的两个分割开的区间的乘积,所以这个乘积可能为两个最大的数相乘,也有可能为两个最小的数相乘(两个负数)。
2、实现细节维度
每次都要建树,不然会错误。
核心代码
node U(node x, node y) {
node z;
z.l = x.l, z.r = y.r, z.sum = x.sum + y.sum;
z.maxn = max({x.maxn, y.maxn, x.sum2 + y.sum1});
z.minn = min({x.minn, y.minn, x.sum4 + y.sum3});
z.sum1 = max(x.sum1, x.sum + y.sum1);
z.sum2 = max(y.sum2, y.sum + x.sum2);
z.sum3 = min(x.sum3, x.sum + y.sum3);
z.sum4 = min(y.sum4, y.sum + x.sum4);
return z;
}
for (int i = 1; i < n; i++) {
node x = Q(1, 1, i), y = Q(1, i + 1, n);
ans = max({ans, x.maxn * y.maxn, x.minn * y.minn});
}
3、数学建模维度
复杂度计算:
- 建树:\(O(n)\)
- 查询次数:\(O(n)\)
- 查询:\(O(\log n)\)
- 总复杂度:\(O(n\log n)\)
B - 玩偶
1、算法思想维度
问题类型:线段树二分
做题思路:每次枚举最大的玩具大小,然后进行线段树二分即可。
2、实现细节维度
请写线段树二分而不是二分+线段树。
核心代码
for (int i = 1; i <= n; i++) {
if (t[i].h != t[i - 1].h || (t[i].h == t[i - 1].h && t[i].c != t[i - 1].c)) {
a[++idx] = t[i];
} else {
a[idx].p += t[i].p;
}
}
for (int i = idx; i >= 1; i--) {
suf[i] = suf[i + 1] + a[i].c * a[i].p;
}
for (int i = 1, j, cnt; i <= idx; i = j) {
cnt = 0;
for (j = i; j <= idx && a[i].h == a[j].h; j++) {
sum += a[j].p, cnt += a[j].p;
}
if (sum - 2 * cnt + 1 <= 0) {
ans = min(ans, suf[j]);
} else {
int x = Q(1, sum - 2 * cnt + 1);
ans = min(ans, Q2(1, 1, x - 1) + (sum - 2 * cnt + 1 - Q(1, 1, x - 1)) * x + suf[j]);
}
for (int k = i; k < j; k++) {
G(1, a[k].c, a[k].p);
}
}
3、数学建模维度
复杂度计算:
- 排序:\(O(n\log n)\)
- 操作次数:\(O(n)\)
- 线段树二分:\(O(\log n)\)
- 总复杂度:\(O(n\log n)\)
C - 无人机
1、算法思想维度
问题类型:\(\text{Dijkstra}\)
做题思路:从前面和后面分别做一次 \(\text{Dijkstra}\),然后找中间节点更新答案即可。
2、实现细节维度
不要用大根堆,要用小根堆。
核心代码
for (q.push({0, 1}), dis[1] = 0; !q.empty();) {
int x = q.top().second, d = q.top().first;
q.pop();
if (!vis[x]) {
vis[x] = 1;
for (int i = 0; i < e[x].size(); i++) {
if (dis[e[x][i]] > max(d + 1, a[e[x][i]])) {
dis[e[x][i]] = max(d + 1, a[e[x][i]]), q.push({dis[e[x][i]], e[x][i]});
}
}
}
}
for (q.push({0, n}), dis2[n] = 0; !q.empty();) {
int x = q.top().second, d = q.top().first;
q.pop();
if (!vis2[x]) {
vis2[x] = 1;
for (int i = 0; i < e[x].size(); i++) {
if (dis2[e[x][i]] > max(d + 1, a[e[x][i]])) {
dis2[e[x][i]] = max(d + 1, a[e[x][i]]), q.push({dis2[e[x][i]], e[x][i]});
}
}
}
}
for (int i = 1; i <= n; i++) {
for (int j = 0; j < e[i].size(); j++) {
if (dis[i] != dis2[e[i][j]]) {
ans = min(ans, 2 * max(dis[i], dis2[e[i][j]]));
} else {
ans = min(ans, 2 * dis[i] + 1);
}
}
}
3、数学建模维度
复杂度计算:
- \(\text{Dijkstra}\):\(O(n\log n)\)
- 计算答案:\(O(n+m)\)
- 总复杂度:\(O(n\log n)\)
数据结构 \(3\)
A - Pashmak and Parmida's problem
1、算法思想维度
问题类型:线段树,\(\text{mp}\)
做题思路:\(\text{mp}\) 处理 \(f\) 的值,对于每个 \(i\),用线段树计算即可。
2、实现细节维度
核心代码
void B(int id, int l, int r) {
tr[id] = {l, r, 0};
if (l == r) {
return tr[id].sum = l == 1, void(0);
}
int mid = l + r >> 1;
B(id << 1, l, mid), B(id << 1 | 1, mid + 1, r), tr[id].sum = tr[id << 1].sum + tr[id << 1 | 1].sum;
}
int Q(int id, int l, int r) {
if (l <= tr[id].l && tr[id].r <= r) {
return tr[id].sum;
}
int mid = tr[id].l + tr[id].r >> 1;
return r < tr[id].l || l > tr[id].r ? 0 : Q(id << 1, l, r) + Q(id << 1 | 1, l, r);
}
void G(int id, int x) {
if (tr[id].l == tr[id].r) {
return tr[id].sum++, void(0);
}
int mid = tr[id].l + tr[id].r >> 1;
(x <= mid) ? (G(id << 1, x)) : (G(id << 1 | 1, x)), tr[id].sum = tr[id << 1].sum + tr[id << 1 | 1].sum;
}
B(1, 1, n), mp.clear(), mp[a[n]]++;
for (int i = n - 1; i >= 1; i--) {
ans += Q(1, 1, st[i] - 1), mp[a[i]]++, G(1, mp[a[i]]);
}
3、数学建模维度
复杂度计算:
- 建树:\(O(n)\)
- 查询次数:\(O(n)\)
- 线段树:\(O(\log n)\)
- 总复杂度:\(O(n\log n)\)
B - Max to the Right of Min
1、算法思想维度
问题类型:枚举,启发式分裂
做题思路:直接枚举右端点 \(r\),找出 \([x,r]\) 区间上的最小值 \(m_1\),然后找出 \(m_1\) 左端第一个比它小的数 \(m_2\),则左端点把 \(m_2\) 放进去就可以了。我们可以通过比较左端点的可能值个数和右端点的可能值个数,并选择少的枚举即可。
2、实现细节维度
写代码时要分清 \(lmi,lma,rmi,rma\) 这四个数组的用处。
核心代码
void S1(int l, int x, int r) {
int e = 1e9, y;
for (int i = x - 1; i >= l; i--) {
(a[i] < e) && (e = a[i], y = rmi[i]), ans += min(y - x - 1, r - x);
}
}
void S2(int l, int x, int r) {
int e = 1e9, y;
for (int i = x + 1; i <= r; i++) {
(a[i] < e) && (e = a[i], y = lmi[i]), ans += max(0ll, y - l + 1);
}
}
for (int i = 1; i <= n; i++) {
for (; top && a[i] < a[st[top]]; top--);
lmi[i] = st[top], st[++top] = i;
}
top = st[0] = 0;
for (int i = 1; i <= n; i++) {
for (; top && a[i] > a[st[top]]; top--);
lma[i] = st[top], st[++top] = i;
}
top = 0, st[0] = n + 1;
for (int i = n; i >= 1; i--) {
for (; top && a[i] < a[st[top]]; top--);
rmi[i] = st[top], st[++top] = i;
}
top = 0, st[0] = n + 1;
for (int i = n; i >= 1; i--) {
for (; top && a[i] > a[st[top]]; top--);
rma[i] = st[top], st[++top] = i;
}
for (int i = 1; i <= n; i++) {
int x = lma[i] + 1, y = rma[i] - 1;
(i - x + 1 <= y - i + 1) ? (S1(x, i, y)) : (S2(x, i, y));
ans += i - x;
}
3、数学建模维度
复杂度计算:
- 预处理:\(O(n)\)
- 计算答案:\(O(n\log n)\)
- 总复杂度:\(O(n\log n)\)
C - Easy problem I
1、算法思想维度
问题类型:线段树
做题思路:由于 \(x\) 单调递增,所以对于 \(i\),在某个分界点后对他的所有操作都会变成 \(a_i=x-a_i\),这个区间修改可以用线段树维护。在此之前,所有对他的操作都是 \(a_i=a_i-x\),这个也可以用线段树维护。在两类操作分界点之间暴力修改即可。
2、实现细节维度
多组数据,所以每次都要建 \(1\sim n\) 的树。
核心代码
void U(int id) {
tr[id].lazy2 = tr[id].lazy3 = 0, tr[id].lazy1 = 1;
tr[id].minv = min(tr[id << 1].minv, tr[id << 1 | 1].minv);
tr[id].cnt = tr[id << 1].cnt + tr[id << 1 | 1].cnt;
tr[id].s1 = tr[id << 1].s1 + tr[id << 1 | 1].s1;
tr[id].s2 = tr[id << 1].s2 + tr[id << 1 | 1].s2;
}
void G1(int id, int v) { tr[v].minv -= tr[id].lazy2, tr[v].s1 -= tr[v].cnt * tr[id].lazy2, tr[v].lazy2 += tr[id].lazy2; }
void G2(int id, int v) {
tr[v].s2 = (tr[v].r - tr[v].l + 1 - tr[v].cnt) * tr[id].lazy3 + tr[id].lazy1 * tr[v].s2;
tr[v].lazy3 = tr[id].lazy3 + tr[id].lazy1 * tr[v].lazy3, tr[v].lazy1 *= tr[id].lazy1;
}
void D(int id) {
(tr[id].lazy2) && (G1(id, id << 1), G1(id, id << 1 | 1), 0);
(tr[id].lazy3) && (G2(id, id << 1), G2(id, id << 1 | 1), 0);
tr[id].lazy2 = tr[id].lazy3 = 0, tr[id].lazy1 = 1;
}
void B(int id, int l, int r) {
if (l == r) {
return tr[id] = {l, r, w[l], 0, w[l], 1, 1, 0, 0}, void(0);
}
int mid = l + r >> 1;
tr[id] = {l, r}, B(id << 1, l, mid), B(id << 1 | 1, mid + 1, r), U(id);
}
void G(int id, int l, int r, int x) {
if (l <= tr[id].l && tr[id].r <= r) {
if (tr[id].minv >= x) {
tr[id].minv -= x, tr[id].s1 -= tr[id].cnt * x, tr[id].s2 = (tr[id].r - tr[id].l + 1 - tr[id].cnt) * x - tr[id].s2, tr[id].lazy2 += x, tr[id].lazy3 = x - tr[id].lazy3, tr[id].lazy1 *= -1;
} else {
if (tr[id].l == tr[id].r) {
tr[id].s2 = x - tr[id].s1, tr[id].cnt = tr[id].s1 = 0, tr[id].minv = 2e18;
} else {
int mid = tr[id].l + tr[id].r >> 1;
D(id), (l <= mid) && (G(id << 1, l, r, x), 0);
(r > mid) && (G(id << 1 | 1, l, r, x), 0), U(id);
}
}
return;
}
int mid = tr[id].l + tr[id].r >> 1;
D(id), (l <= mid) && (G(id << 1, l, r, x), 0);
(r > mid) && (G(id << 1 | 1, l, r, x), 0), U(id);
}
int Q(int id, int l, int r) {
if (l <= tr[id].l && tr[id].r <= r) {
return tr[id].s1 + tr[id].s2;
}
int mid = tr[id].l + tr[id].r >> 1, ans = 0;
D(id);
(l <= mid) && (ans = Q(id << 1, l, r));
(r > mid) && (ans += Q(id << 1 | 1, l, r));
return ans;
}
3、数学建模维度
复杂度计算:
- 建树:\(O(n)\)
- 查询次数:\(O(n)\)
- 线段树:\(O(\log n)\)
- 总复杂度:\(O(n\log n)\)
D - 序列
1、算法思想维度
问题类型:分块
做题思路:可以直接离线,然后扫描序列,然后数据结构维护时间。对于一个修改,可以将它拆成 \((l,t,val)\) 和 \((r+1,t,−val)\)。然后就在 \(l\) 时对维护的时间上 \(t\sim q\) 的范围整体加 \(val\) 然后到 \(r+1\) 时对 \(t\sim q\) 的范围整体加上 \(−val\)。然后就是一个区间加区间查询排名问题,分块维护即可。
2、实现细节维度
核心代码
void M(int l, int x) {
for (int i = L[id[l]]; i <= R[id[l]]; i++) {
(a[i].pos >= l) && (a[i].v += x);
}
sort(a + L[id[l]], a + R[id[l]] + 1);
for (int i = id[l] + 1; i <= (q - 1) / kMaxM + 1; i++) {
tag[i] += x;
}
}
int Q(int r, int x) {
int ans = 0;
for (int i = L[id[r]]; i <= R[id[r]]; i++) {
ans += a[i].pos <= r && a[i].v + tag[id[r]] >= x;
}
for (int i = 1; i < id[r]; i++) {
ans += R[i] - (lower_bound(a + L[i], a + R[i] + 1, Node({0, x - tag[i]})) - a) + 1;
}
return ans;
}
for (int i = 1; i <= q; i++) {
a[i] = {i, 0}, id[i] = (i - 1) / kMaxM + 1;
}
for (int i = 1; i <= (q - 1) / kMaxM + 1; i++) {
L[i] = kMaxM * (i - 1) + 1, R[i] = min(kMaxM * i, q);
}
for (int i = 1; i <= n; i++) {
for (node t : e[i]) {
(t.t == 1) ? (M(t.pos, t.x), 0) : (ans[t.pos] = Q(t.pos - 1, t.x));
}
}
3、数学建模维度
复杂度计算:
- 预处理:\(O(q)\)
- 分块处理:\(O(n\sqrt{n})\)
- 总复杂度:\(O(n\sqrt{n})\)
E - A. 数据结构
1、算法思想维度
问题类型:扫描线,树状数组
做题思路:这道题目相当于求有多少个颜色不出现在 \([l,r]\) 里面,所以用扫描线。发现,当一个颜色不合法当且仅当 \(l\in(pre_{c_i-1},st_{c_i}\),如果没有 \(c_i\) 但是有 \(c_i-1\),还得加上这个颜色,所以要用树状数组维护扫描线。
2、实现细节维度
核心代码
for (int i = 1; i <= n; i++) {
cin >> a[i], l[a[i]] = min(l[a[i]], i), r[a[i]] = max(r[a[i]], i), v[a[i]].push_back(i);
}
for (int i = 0; i <= n + 1; i++) {
v[i].push_back(n + 1);
}
for (int i = 1; i <= n + 1; i++) {
if (l[i] == n + 1) {
for (int j = 0; j < v[i - 1].size() - 1; j++) {
int x = v[i - 1][j] + 1, y = v[i - 1][j + 1] - 1;
G(x, y, x, y, i);
}
} else {
int L = 0, R = n + 1, flag = 0;
for (auto x : v[i - 1]) {
if (l[i] <= x && x <= r[i]) {
flag = 1;
break;
}
(x < l[i]) && (L = max(x, L)), (x > r[i]) && (R = min(x, R));
}
(!flag) && (G(L + 1, l[i], r[i], R - 1, i), 0);
}
}
for (int i = 1, x, y; i <= m; i++) {
cin >> x >> y, q[y].push_back({x, i});
}
for (int i = 1; i <= n; i++) {
for (int j = 0; j < e[i].size(); j++) {
A(e[i][j].l, e[i][j].v), A(e[i][j].r + 1, -e[i][j].v);
}
for (int j = 0; j < q[i].size(); j++) {
res[q[i][j].second] = n + 1 - Q(q[i][j].first);
}
}
3、数学建模维度
复杂度计算:
- 树状数组:\(O(\log m)\)
- 扫描线:\(O(m)\)
- 总复杂度:\(O(m\log m)\)

浙公网安备 33010602011771号