2025/7/25 模拟赛总结

\(100+100+25+35=260\),没挂分就是赢!

赛时记录:

\(3:46:03\) 写完 A(\(100\)

\(2:44:53\) B 假了

\(2:17:53\) 写完 D 15pts(\(115\)

\(1:20:11\) 写完 B(\(215\)

\(1:06:53\) 写完 D 35pts(\(235\)

\(0:50:56\) 写完 C 25pts(\(260\)

#A. 子段乘积

简单题,枚举左右区间的端点,答案为最大子段和的积与最小子段和的积的最大值

赛时不记得线性求前后缀最大子段和了,于是写了线段树

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

using namespace std;
using LL = long long;

const int kMaxN = 2e5 + 5;

struct P {
  LL s, lt, rt, t;
};

LL T, n, a[kMaxN], ans = -1e18;

struct SegTree {
  P p[kMaxN << 2];
  P pushup(P l, P r) { return (P){l.s + r.s, max(l.lt, l.s + r.lt), max(r.rt, r.s + l.rt), max({l.t, r.t, l.rt + r.lt})}; }
  void update(int qx, int x, int l, int r, LL k) {
    if (l == r) {
      return p[x] = (P){k, k, k, k}, 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] = pushup(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) {
      P L = query(ql, qr, x << 1, l, mid), R = query(ql, qr, x << 1 | 1, mid + 1, r);
      return pushup(L, 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);
    }
  }
  void init(int n) {
    for (int i = 1; i <= n << 2; i++) {
      p[i] = (P){0, 0, 0, 0};
    }
  }
} tr1, tr2;

int main() {
  ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
  for (cin >> T; T; T--, tr1.init(n), tr2.init(n), ans = -1e18) {
    cin >> n;
    for (int i = 1; i <= n; i++) {
      cin >> a[i], tr1.update(i, 1, 1, n, a[i]), tr2.update(i, 1, 1, n, -a[i]);
    }
    for (int i = 1; i < n; i++) {
      LL l = tr1.query(1, i, 1, 1, n).t, r = tr1.query(i + 1, n, 1, 1, n).t;
      LL x = tr2.query(1, i, 1, 1, n).t, y = tr2.query(i + 1, n, 1, 1, n).t;
      ans = max({ans, l * r, x * y});
    }
    cout << ans << '\n';
  }
  return 0;
}

#B. 玩偶

比较抽象的题,写了一个小时发现题看错了,但是要改的只有两行。好多人写的都是单 \(\log\) 权值线段树,那我们双 \(\log\) 小常数树状数组二分怎么你了。绝对不是我忘记权值线段树怎么写了

对所有玩偶按 \(c\) 从大到小排序,用两个树状数组存 \(p_i\)\(p_i\times c_i\) 的前缀和

\(5\times 10^5\) 倒序向 \(1\) 枚举,用 set 存下每个数在哪些位置出现了。设当前枚举到了 \(i\)。严格大于一半代表其他玩偶的数量总和小于 \(i\) 的数量,用数组 \(c,u\) 分别存每一种玩偶的 \(\sum p_i,\sum p_i\times c_i\)

tot 为所有玩偶的损失之和,\(p,q\) 为比 \(i\) 大的所有玩偶的 \(\sum p_i,\sum p_i\times c_i\)

一开始将所有 \(p_i,p_i\times c_i\) 插入树状数组。枚举过程中将这些数删掉。二分能保留的最大的玩偶,去最小值即可

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

using namespace std;
using LL = long long;

const int kMaxN = 5e5 + 5;

struct P {
  LL h, c, p;
  bool operator<(const P &o) const { return c != o.c ? c > o.c : h < o.h; }
} a[kMaxN];

LL n, c[kMaxN], f[kMaxN], d[kMaxN], p[kMaxN], u[kMaxN], cnt, tot, t[2][kMaxN], cur = -1, L, R, ans = 1e18, w, q;
set<int> s[kMaxN];

void update(int op, int x, LL k) {
  for (; x <= n; x += x & -x) {
    t[op][x] += k;
  }
}
LL query(int op, int x, LL ret = 0) {
  for (; x; x -= x & -x) {
    ret += t[op][x];
  }
  return ret;
}

int main() {
  ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
  cin >> n;
  for (int i = 1; i <= n; i++) {
    cin >> a[i].h >> a[i].c >> a[i].p, c[a[i].h] += a[i].p, u[a[i].h] += a[i].p * a[i].c, cnt += a[i].p, tot += a[i].p * a[i].c;
  }
  sort(a + 1, a + n + 1);
  for (int i = 1; i <= n; i++) {
    s[a[i].h].insert(i), update(0, i, a[i].p * a[i].c), update(1, i, a[i].p);
  }
  for (int i = 5e5; i; i--) {
    if (s[i].empty()) {
      continue;
    }
    c[i]--;
    if (cnt - (c[i] + 1) - w <= c[i]) {
      ans = min(ans, q), w += c[i] + 1, q += u[i];
      continue;
    }
    for (int pos : s[i]) {
      update(1, pos, -a[pos].p), update(0, pos, -(a[pos].p * a[pos].c));
    }
    L = 1, R = n;
    for (int mid = L + R >> 1; L < R; mid = L + R >> 1) {
      query(1, mid) >= c[i] ? R = mid : L = mid + 1;
    }
    LL lst = query(1, L) - c[i];
    f[i] = tot - (query(0, L) - a[L].c * lst) - u[i], ans = min(ans, f[i]);
    w += c[i] + 1, q += u[i];
  }
  cout << ans << '\n';
  return 0;
}

#C. 无人机

显然平着飞的次数最多为 \(1\),否则可以先向上再向下,这样一定不会更劣

于是可以从 \(1,n\) 开始做两次 Dijkstra,枚举平着飞的那一条边或没有平飞时的顶点,求 \(\min\) 即可

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

using namespace std;
using LL = long long;

const int kMaxN = 2e5 + 5;

struct P {
  LL u, f;
  bool operator<(const P &o) const { return f > o.f; }
};

LL n, m, a[kMaxN], ans = 1e18, cur, dis[2][kMaxN], vis[kMaxN];
vector<int> g[kMaxN];
priority_queue<P> q;

void Dij(int op, int s) {
  for (fill(vis, vis + kMaxN, 0), fill(dis[op], dis[op + 1], 1e18); !q.empty(); q.pop()) {
  }
  for (q.push({s, 0}), dis[op][s] = 0; !q.empty();) {
    auto [u, f] = q.top();
    q.pop();
    if (!vis[u]) {
      vis[u] = 1;
      for (int v : g[u]) {
        dis[op][v] > max(a[v], f + 1) && (q.push({v, max(a[v], f + 1)}), dis[op][v] = max(a[v], f + 1));
      }
    }
  }
}

int main() {
  ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
  cin >> n >> m;
  for (int i = 1; i <= n; i++) {
    cin >> a[i];
  }
  for (int i = 1, u, v; i <= m; i++) {
    cin >> u >> v, g[u].push_back(v), g[v].push_back(u);
  }
  Dij(0, 1), Dij(1, n);
  for (int i = 1; i <= n; i++) {
    ans = min(ans, max(dis[0][i], dis[1][i]) << 1);
    for (int j : g[i]) {
      ans = min(ans, max(dis[0][i], dis[1][j]) << 1 | 1);
    }
  }
  cout << ans << '\n';
  return 0;
}

#D. 交集

先考虑两个区间是包含关系,则它们要么在同一组,要么大区间单独一组,所以我们先把所有互不包含的区间求出来

\(dp_{i,j}\) 为考虑前 \(i\) 个区间,分成 \(j\) 段的最大总和,使用单调队列优化 dp 即可

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

using namespace std;

const int kMaxN = 5e3 + 5;

struct P {
  int l, r;
  bool operator<(const P &o) const { return l != o.l ? l < o.l : r > o.r; }
} a[kMaxN], c[kMaxN];
struct S {
  bool operator()(const int &l, const int &r) { return a[l].r < a[r].r; }
};

int n, k, dp[kMaxN][kMaxN], tot, p, l[kMaxN], q[kMaxN << 1], hd, tl, ans, vis[kMaxN];
priority_queue<int, vector<int>, S> Q;

int main() {
  ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
  cin >> n >> k, fill(dp[0], dp[kMaxN], -2.1e9), dp[0][0] = 0;
  for (int i = 1; i <= n; i++) {
    cin >> a[i].l >> a[i].r;
  }
  sort(a + 1, a + n + 1);
  for (int i = 1; i <= n; i++) {
    for (; !Q.empty() && a[Q.top()].r >= a[i].r; vis[Q.top()] = 1, Q.pop()) {
    }
    Q.push(i);
  }
  for (int i = 1; i <= n; i++) {
    vis[i] ? (l[++p] = a[i].r - a[i].l + 1) : (c[++tot] = a[i], 0);
  }
  for (int j = 1; j <= k; j++) {
    q[hd = tl = 1] = 0;
    for (int i = 1; i <= tot; i++) {
      for (; hd <= tl && c[q[hd] + 1].r < c[i].l; hd++) {
      }
      dp[i][j] = dp[q[hd]][j - 1] + c[q[hd] + 1].r - c[i].l + 1;
      for (; hd <= tl && dp[q[tl]][j - 1] + c[q[tl] + 1].r <= dp[i][j - 1] + c[i + 1].r; tl--) {
      }
      q[++tl] = i;
    }
  }
  sort(l + 1, l + p + 1, greater<int>());
  for (int i = 1, w = dp[tot][i]; i <= k; i++, w = dp[tot][i]) {
    if (k - i <= p) {
      for (int j = 1; j <= k - i; j++) {
        w += l[j];
      }
      ans = max(ans, w);
    }
  }
  cout << ans << '\n';
  return 0;
}
posted @ 2025-07-25 17:21  BluemoonQwQ  阅读(31)  评论(0)    收藏  举报