比赛链接

2025牛客寒假算法基础集训营1

B.一气贯通之刃

题目描述

小红拿到了一棵树,她想请你寻找一条简单路径,使得这条路径不重不漏的经过所有节点。如果不存在这样的简单路径,则直接输出−1。

简单路径是指这样一条路径,其经过的顶点和边互不相同。

解题思路

只需判断这棵树是否为一条链:一条链两端的节点度数=1,其他节点度数=2。

#include<bits/stdc++.h>
using namespace std;

int n;
vector<int>ans, deg(200005, 0);

int main() {
	cin >> n;
	for (int i = 1; i < n; i++) {
		int u, v;
		cin >> u >> v;
		deg[u]++;
		deg[v]++;
	}
	for (int i = 1; i <= n; i++) {
		if (deg[i] != 2) {
			if (deg[i] == 1) ans.push_back(i);
			else {
				cout << -1 << endl;
				return 0;
			}
		}
	}

	if (ans.size() == 2) cout << ans[0] << ' ' << ans[1] << endl;
	else cout << -1 << endl;
	return 0;
}

E.双生双宿之错

题目描述

小红定义一个数组是“双生数组”,当且仅当该数组大小为偶数,数组的元素种类恰好为 2 种,且这两种元素的出现次数相同。例如 { 1, 1, 4, 4, 1, 4 } 是双生数组。

现在小红拿到了一个长度为偶数的数组,她可以进行若干次操作,每次操作将选择一个元素,使其加 1 或者减 1。小红希望你计算将该数组变成双生数组的最小操作次数。

解题思路

中位数有这样的性质 :所有数与中位数的绝对差之和最小。

先将数组排序,前半部分取中位数,后半部分也取中位数,分别让两部分的数向中位数靠拢。需要注意的是,两个中位数相等时不符合题意,需要将一个中位数减一,或者另一个中位数加一,判断两种情况谁的操作次数更小。

#include<bits/stdc++.h>
#define int long long
using namespace std;

int n;

static void solve() {
	cin >> n;
	vector<int> a(n + 1);
	for (int i = 1; i <= n; i++) cin >> a[i];
	sort(a.begin(), a.end());
	//中位数
	int left = a[(n / 2 + 1) / 2], right = a[(n / 2 + 1 + n) / 2];
	int ans = 0;
	if (left != right) {
		for (int i = 1; i <= n / 2; i++) ans += abs(a[i] - left);
		for (int i = n / 2 + 1; i <= n; i++) ans += abs(a[i] - right);
	}
	else {
		int res = 0;
		left--;
		for (int i = 1; i <= n / 2; i++) res += abs(a[i] - left);
		for (int i = n / 2 + 1; i <= n; i++) res += abs(a[i] - right);
		left++;
		right++;
		for (int i = 1; i <= n / 2; i++) ans += abs(a[i] - left);
		for (int i = n / 2 + 1; i <= n; i++) ans += abs(a[i] - right);
		right--;
		ans = min(res, ans);
	}
	cout << ans << endl;
}

signed main() {
	int T = 1;
	cin >> T;
	while (T--) {
		solve();
	}
	return 0;
}

G.井然有序之衡

题目描述

小红拿到了一个数组,她可以进行任意次以下操作:选择两个元素,使得其中一个加 1,另一个减 1。

小红希望最终数组变成一个排列,请你帮助他确定这能否实现。如果可以实现的话,还需要求出最小操作次数。

长度为 n 的排列是由 1∼n 这 n 个整数、按任意顺序组成的数组,其中每个整数恰好出现一次。例如,{2,3,1,5,4} 是一个长度为 5 的排列,而 {1,2,2} 和 {1,3,4} 都不是排列,因为前者存在重复元素,后者包含了超出范围的数。

解题思路

当一个排列确定时,其必包含1~n,其和也唯一。

由于给定的操作无法改变数组的和+1,-1相互抵消), 因此可以先判断给定数组的和是否与长度为n的排列的和相同,不相同的直接输出-1

随后将数组升序排序,每一位与1~n的排列对应,找出数组比排列大的数累加差值为t。由于总和相同,剩下的数组比排列小的数,其差值一定与t相同。因此共有t次操作,即t组{+1,-1}。

#define int long long
using namespace std;

int sum, aim, n, t;
vector<int> a;
static void solve() {
	cin >> n;
	a.push_back(0);
	for (int i = 1; i <= n; i++) {
		cin >> t;
		sum += t;
		a.push_back(t);
	}
	if (n % 2 == 1) aim = (1 + n) / 2 * n;
	else aim = (1 + n) * (n / 2);

	if (sum == aim) {
		int t = 0;
		sort(a.begin(), a.end());
		for (int i = 1; i <= n; i++) {
			if (a[i] > i)t += a[i] - i;
		}
		cout << t << endl;
	}
	else
		cout << -1 << endl;
}

signed main() {
	int T = 1;
	// cin >> T;
	while (T--) {
		solve();
	}
	return 0;
}

H.井然有序之窗

题目描述

小红希望你构造一个长度为 n 的排列,需要满足第 i 个元素的范围在 [ li, ri ] 范围内。你能帮帮她吗?

长度为 n 的排列是由 1∼n 这 n 个整数、按任意顺序组成的数组,其中每个整数恰好出现一次。例如,{2,3,1,5,4} 是一个长度为 5 的排列,而 {1,2,2} 和 {1,3,4} 都不是排列,因为前者存在重复元素,后者包含了超出范围的数。

解题思路

1.将给出的 [ li, ri ] 存储在结构体中,同时存储其编号。

struct range {
	int l, r, index;
}ran[100005];

2.按照区间右端点升序,右端点相同的按照左端点降序。

bool cmp(range a, range b) {
	if (a.r == b.r) return a.l > b.l;
	else return a.r < b.r;
}

3.定义一个set,存储未使用的数。

set<int> unused;
for (int i = 1; i <= n; i++) unused.insert(i);

4.遍历排序好的区间,使用 lower_bound(l) 从 set 中查找未使用的数并填入 ans 数组。若 lower_bound() 找不到合法的数或者找到的数超过右区间,则不存在满足条件的排列,直接输出 −1。

完整代码如下:

#include<bits/stdc++.h>
#define int long long
using namespace std;

struct range {
	int l, r, index;
}ran[100005];

bool cmp(range a, range b) {
	if (a.r == b.r)return a.l > b.l;
	else return a.r < b.r;
}

static void solve() {
	int n; cin >> n;
	for (int i = 1; i <= n; i++) {
		cin >> ran[i].l >> ran[i].r;
		ran[i].index = i;
	}
	sort(ran + 1, ran + 1 + n, cmp);
  
	set<int> unused;
	for (int i = 1; i <= n; i++)unused.insert(i);
  
	vector<int> ans(100005, 0);
	for (int i = 1; i <= n; i++) {
		//获取区间边界
		int l = ran[i].l, r = ran[i].r;
		//查找第一个未使用的大于等于l的数
		auto it = unused.lower_bound(l);
		//未找到或大于右边界
		if (it == unused.end() || *it > r) {
			cout << -1 << endl;
			return;
		}
		//找到,填入此数
		ans[ran[i].index] = *it;
		//从未使用中删除
		unused.erase(it);
	}

	for (int i = 1; i <= n; i++) {
		cout << ans[i] << " ";
	}
}

signed main() {
	int T = 1;
	// cin >> T;
	while (T--) {
		solve();
	}
	return 0;
}

如有错误欢迎指正~