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)\)
posted @ 2025-07-25 10:59  小熊涛涛  阅读(17)  评论(0)    收藏  举报