SMU winter 2025 Personal Round 3

SMU winter 2025 Personal Round 3

A. Vasya and Book

思路

计算从起始页 \(x\) 到目标页 \(y\) 的最小按键次数。每次按键可向前或向后滚动 \(d\) 页,但不可越界。解题关键在于分析三种可能路径:

  1. 直接移动:若 \(|x - y|\) 能被 \(d\) 整除,则直接移动,次数为 \(\frac{|x - y|}{d}\)
  2. 经第一页移动:若 \(y\) 到第一页的步数 \((y-1)\) 能被 \(d\) 整除,则总次数为从 \(x\) 到第一页的步数加上从第一页到 \(y\) 的步数。
  3. 经最后一页移动:若最后一页到 \(y\) 的步数 \((n - y)\) 能被 \(d\) 整除,则总次数为从 \(x\) 到最后一页的步数加上从最后一页到 \(y\) 的步数。

取上述三种情况的最小值即可。

代码

#include <bits/stdc++.h>

using namespace std;

using i64 = long long;

void solve() {

	int n, d, x, y;
	cin >> n >> x >> y >> d;

	int ans = 1 << 30;
	if (abs(x - y) % d == 0) {
		ans = min(ans, abs(x - y) / d);
	}
	if ((y - 1) % d == 0) {
		ans = min(ans, (y - 1) / d + (x + d - 1) / d);
	}
	if ((n - y) % d == 0) {
		ans = min(ans, (n - y) / d + (n - x + d - 1) / d);
	}

	if (ans == 1 << 30) {
		ans = -1;
	}

	cout << ans << "\n";

}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);

	int t;
	cin >> t;
	while (t--) {
		solve();
	}

	return 0;
}

B. Vova and Trophies

思路

预处理前缀数组 \(\text{pre}\) 和后缀数组 \(\text{suf}\),分别表示以位置 \(i\) 结尾和开头的最长连续 'G' 的长度。遍历每个位置,若当前位置为 'S',则其左右两侧的连续 'G' 可合并为 \(\text{pre}[i-1] + \text{suf}[i+1]\)。若总 'G' 数 \(\text{cnt}\) 大于该值,则存在可交换的 'G',总长度可再加 \(1\)。最终取所有情况的最大值,并处理全为 'G' 的特殊情况即可。

代码

#include <bits/stdc++.h>

using namespace std;

using i64 = long long;

int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);

	int n;
	cin >> n;

	string s;
	cin >> s;

	s = " " + s;

	int cnt = 0, ans = 0;
	vector<int> pre(n + 1), suf(n + 2);
	for (int i = 1; i <= n; i ++) {
		cnt += s[i] == 'G';
		if (s[i] == 'G') {
			pre[i] = pre[i - 1] + 1;
			ans = max(ans, pre[i]);
		}
	}
	for (int i = n; i > 0; i --) {
		if (s[i] == 'G') {
			suf[i] = suf[i + 1] + 1;
		}
	}

	for (int i = 1; i <= n; i ++) {
		if (s[i] != 'G') {
			ans = max(ans, pre[i - 1] + suf[i + 1] + (cnt - pre[i - 1] - suf[i + 1] > 0));
		}
	}

	cout << ans << "\n";

	return 0;
}

C. Multi-Subject Competition

思路

对于每个科目,将学生按技能降序排序,计算前缀和 \(x_j\)(前 \(j\) 项的和)。对于每个可能的 \(k\)(选取人数),累加所有科目中前 \(k\) 项的正前缀和。最终取所有 \(k\) 对应累加和的最大值。具体地,若科目 \(s\) 的前缀和 \(x_j\) 非负,则将其累加至 \(\text{has}[j]\),否则停止。最终答案即 \(\max(\text{has})\),若所有情况均为负则输出 \(0\)

代码

#include <bits/stdc++.h>

using namespace std;

using i64 = long long;

int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);

	int n, m;
	cin >> n >> m;

	vector g(m, vector<i64>());
	for (int i = 0; i < n; i ++) {
		int s, r;
		cin >> s >> r;
		s--;
		g[s].emplace_back(r);
	}

	vector<i64> has(max(n, m));
	for (auto &x : g) {
		if (x.empty()) continue;
		sort(x.begin(), x.end(), greater<>());
		for (int j = 0; j < x.size(); j ++) {
			if (j) {
				x[j] += x[j - 1];
			}
			if (x[j] > 0) {
				has[j] += x[j];
			} else {
				break;
			}
		}
	}

	cout << *max_element(has.begin(), has.end()) << "\n";

	return 0;
}

D. Maximum Diameter Graph

思路

首先检查总度数是否满足 \(\sum a_i \geq 2(n-1)\),否则直接输出“NO”。将度数大于 \(1\) 的顶点按降序排列并连接成主干链,其初始直径为 \(k-1\)\(k\) 为主干顶点数)。优先将度数为 \(1\) 的叶子连接到主干两端,每端最多添加一个叶子,直径最多增加 \(2\),最终直径为 \(k + t - 1\)\(t\) 为两端实际添加的叶子数,最多 \(2\))。剩余叶子连接到主干中仍有剩余度数的顶点,确保所有度数限制被满足。最终输出最大直径及合法边集。

代码

#include <bits/stdc++.h>

using namespace std;

using i64 = long long;

int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);

	int n;
	cin >> n;

	int sum = 0;
	vector<int> a(n), need;
	vector<pair<int, int>> d;
	for (int i = 0; i < n; i ++) {
		cin >> a[i];
		sum += a[i];
		if (a[i] != 1) {
			d.emplace_back(a[i], i);
		} else {
			need.emplace_back(i);
		}
	}

	if (sum < 2 * (n - 1)) {
		cout << "NO\n";
		return 0;
	}

	vector g(n, vector<int>(n));
	if (d.size() > 1) {
		sort(d.begin(), d.end(), greater<>());
		sort(d.begin() + 1, d.end());
		for (int i = 0; i + 1 < d.size(); i ++) {
			g[d[i].second][d[i + 1].second] = g[d[i + 1].second][d[i].second] = 1;
			a[d[i].second]--;
			a[d[i + 1].second]--;
		}
	}

	int ans = d.size();
	if (need.size()) {
		for (int i : {0, (int)d.size() - 1}) {
			auto [_, x] = d[i];
			if (a[x] && need.size()) {
				g[x][need.back()] = g[need.back()][x] = 1;
				a[x]--;
				ans ++;
				need.pop_back();
			}
		}
		for (auto [_, s] : d) {
			while (a[s] && need.size()) {
				g[s][need.back()] = g[need.back()][s] = 1;
				a[s]--;
				need.pop_back();
			}
		}
	}

	cout << "YES " << ans - 1 << "\n";
	vector<pair<int, int>> e;
	for (int i = 0; i < n; i ++) {
		for (int j = i + 1; j < n; j ++) {
			if (g[i][j]) {
				e.emplace_back(i + 1, j + 1);
			}
		}
	}
	cout << e.size() << "\n";
	for (auto [x, y] : e) {
		cout << x << " " << y << "\n";
	}

	return 0;
}

E. Increasing Frequency

思路

将问题转化为寻找一个区间 \([l, r]\),使得该区间内某个数 \(x\) 的出现次数与 \(c\) 的出现次数的差最大。

定义前缀差 \(\text{pre}[x]\) 为前 \(j\) 项中 \(x\) 的出现次数减去 \(c\) 的出现次数,并维护每个 \(x\) 的最小前缀差 \(\text{Min}[x]\)。遍历数组时,对于当前位置 \(j\),若当前元素为 \(x\),则 \(\text{delta} = \text{pre}[x] - \text{pre}[c] - \text{Min}[x]\),即区间 \((l, j]\)\(x\)\(c\) 多出的数量。最终答案为原数组中 \(c\) 的数量加上所有 \(\text{delta}\) 的最大值即可。时间复杂度为 \(O(n)\)

代码

#include <bits/stdc++.h>

using namespace std;

using i64 = long long;

int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);

	int n, c;
	cin >> n >> c;

	vector<int> a(n);
	for (auto &i : a) {
		cin >> i;
	}

	int idx = 0;
	vector<int> res(n);
	unordered_map<int, int> pre, Min;
	for (auto i : a) {
		Min[i] = min(Min[i], pre[i] - pre[c]);
		pre[i] ++;
		res[idx++] = pre[i] - pre[c] - Min[i];
	}

	int ans = pre[c];
	for (int i = 0; i < n; i ++) {
		ans = max(ans, res[i] + pre[c]);
	}

	cout << ans << "\n";

	return 0;
}
posted @ 2025-02-16 14:20  Ke_scholar  阅读(20)  评论(0)    收藏  举报