USST-vj-蓝桥杯训练-2025.03.05

感觉难度不是顺序的,感觉是 B < C = E = F < G < A < D < H


A

显然是一道01背包的变体,把不选择改成加上lost[i]即可

关键代码

for (int i = 1; i <= n; ++ i) {
  for (int j = x; j >= 0; -- j) {
	if (j >= u[i]) f[j] = max(f[j] + l[i], f[j - u[i]] + w[i]);
	else f[j] += l[i];
  }
}

B

用高精度-单精度乘法即可,也没什么好讲的,具体原理可以去 OI-Wiki 学习

关键代码

void mul_short(vector<int> &a, int b) {
  for (int i = 0; i < a.size(); ++i) {
    a[i] *= b;
    if (i) {
      a[i] += a[i-1] / 10;
      a[i-1] %= 10;
    }
  }
  while (a.back() >= 10) {
    int tmp = a.back() / 10;
    a.back() %= 10;
    a.push_back(tmp);
  }
}

C

对于第 \(i\) 头牛,查找左右最远的能听到声音的牛,显然可以用二分找到对应区间 \([l,\ r]\)

关键代码

#define all(a) a.begin(), a.end()

for (int i = 0; i < n; i ++) {
  int l = lower_bound(all(p), p[i]-d) - p.begin();
  int r = upper_bound(all(p), p[i]+d) - p.begin() - 1;
  ans += r - l;
}

D

问题描述很清晰,比较考验积累,如果知道相关公式就很简单,难度较高是因为可能很多人不记得,需要现推

第二类斯特林数

可记做 \(S(n,k)\) ,表示将 \(n\) 个两两不同的元素,划分为 \(k\)  个互不区分的非空子集的方案数。
递推式

\[S(n, k) = k\ \cdot S(n-1, k) + S(n-1, k-1) \]

边界值就是 \(S(n, k) = 1\ [n = k]\)\(S(n, k) = 0\ [k=0或k>n]\)

通项公式

\[S(n, k) = \sum^k_{i=0} \frac{(-1)^{k-i}\ i^n}{i!\ (k-i)!} \]

展示一下递推式的写法

int S(int n, int m) {
  if (n == m) return 1;
  if (m == 0 || m > n) return 0;
  return m * S(n-1, m) + S(n-1, m-1);
}

现在算出不区分箱子的情况了,那么答案就是 \(S(n, r) * r!\)


E

数据范围很小,所以暴力即可,枚举每一个城市算出总路费,取最小值即可

关键代码

struct city {
  int cnt, d;
  string name;
  bool operator < (const city &other) const {
    return d < other.d;
  }
};
-----------------------------------------------------
city ans{inf, 0, ""};
for (int i = 0; i < n; i ++) {
  int sum = 0;
  for (int j = 0; j < n; j ++) {
	sum += abs(c[i].d - c[j].d) * c[j].cnt;
  }
  if (sum < ans.cnt) {
	ans = {sum, 0, c[i].name};
  }
}

F

不懂为什么叫增强版,数据范围也不大,纯模拟即可

算出总共种了几棵树苗,最后看看剩下几颗树苗,减一下就好

关键代码

vector<pii> tr(L + 1, {1, 0});
int sum{ 0 }, cnt{ 0 };
while (n --) {
  int op, l, r;
  cin >> op >> l >> r;
  for (int i = l; i <= r; i ++) {
	if (op) {
	  if (!tr[i].first) {
	    sum ++;
	    tr[i].second = 1;
	  }
	  tr[i].first = 1;
    } else {
	  tr[i] = {0, 0};
    }
  }
}
for (int i = 0; i <= L; ++ i) {
  cnt += tr[i].second;
}

G

没啥好说的,01背包板子题,感觉接触过背包的都见过这题了,不了解原理的可以取洛谷搜索“背包九讲”,非常详细

关键代码

vector<int> w(n+1), v(n+1), f(W+1);
for (int i = 1; i <= n; ++ i) {
  for (int j = W; j >= w[i]; -- j) {
	f[j] = max(f[j], f[j-w[i]] + v[i]);
  }
}
cout << f[W];

H

本场最有难度的一题

首先你要知道如何求 最大子段和
原理网上有很多详细讲解,这里贴一下代码

int ans = -inf, sum = 0;
for (int i = 0; i < n; i ++) {
  sum = max(sum + a[i], a[i]);
  ans = max(ans, sum);
}

\(ans\) 即为数组 \(a\) 的最大子段和,这里要求子段不能为空,如果可以为空,则 \(ans\) 初始值修改为 \(0\)

然后进阶到 环状最大子段和
成环后,子段可能有两种情况("-"表示对应区间)

  • 区间不跨端点:........---.....
  • 跨端点:---.........--

第一种情况可以直接求最大子段和,第二种应该怎么搞呢?

假设数列 \(a\) 和为 \(SUM\) 最大子段和为 \(sum\) ,那么显然有 \(SUM - sum\)最小子段和,如果我们知道了最小子段和,也就可以算出第二种情况下的最大子段和

最小子段和怎么求呢?我们只需要把数组 \(a\) 全部取相反数,这时再算最大子段和,那对应的就是原数组的最小子段和。

现在单个区间的最大子段和会算了,那就可以求两个区间了

首先思考可能的区间分布,跨端点的区间显然最多存在一个

  • .....---.......-----........
  • ---........----......----

那么我们维护两个数组 \(f_i\)\(g_i\)

  • \(f_i\)\([1, i]\) 的最大子段和
  • \(g_i\)\([i, n]\) 的最大子段和

然后就可以 \(O(n)\) 的计算 \(f_i+g_{i+1}\) 取最大值

那么在正常情况下我们可以求第一种分布的答案

取相反数的情况下,用 \(SUM - sum\) 我们可以求第二种分布的答案

特例:如果只有一个正数,那么取相反数后就不会得到正确答案,但这中情况下直接当做第一种情况处理即可(即选择正数和绝对值最小的负数,区间长度为1,一定不会绕回起点)

关键代码

int maxsum() {
  for (int i = 1; i <= n; ++ i)
	f[i] = max(f[i-1] + a[i], a[i]);
  for (int i = 1; i <= n; ++ i)
	f[i] = max(f[i-1], f[i]);
  for (int i = n; i > 0; -- i)
	g[i] = max(g[i+1] + a[i], a[i]);
  for (int i = n; i > 0; -- i)
	g[i] = max(g[i+1], g[i]);
  int res{ -inf };
  for (int i = 1; i < n; i ++)
	res = max(res, f[i] + g[i+1]);
  return res;
};
-------------------------------------------------------
int ans = maxsum();
if (cnt == 1) {//正数数量为1
  cout << ans;
} else {
  for_each(all(a), [](int &x){x = -x;}); // 对每个元素取相反数
  sum += maxsum(); // 取反后的最大子段和相当于 (-1 * 元数组对应子段和)
  // 总和去掉这一区间就是 sum - (-1 * 元数组对应子段和),所以最后是相加
  if (!sum) cout << ans; // 如果删掉整个数组,那么直接舍去取相反后的结果
  else cout << max(sum, ans);
}
posted @ 2025-03-06 02:51  夜霧yoki  阅读(36)  评论(0)    收藏  举报