Codeforces Round #867 (Div. 3) (全解全析,尽可能地简单)

文章目录

  • A.TubeTube Feed(贪心)
  • B. Karina and Array(排序,贪心)
  • C. Bun Lover(观察,找规律)
  • D. Super-Permutation(数学、找规律)
  • E. Making Anti-Palindromes(字符串,贪心)
  • F. Gardening Friends(树的直径)
  • G1. Magic Triples (Easy Version)[暴力、数学]
  • G2 - Magic Triples (Hard Version) [值域分治]

传送门

A.TubeTube Feed(贪心)

题意:要求在 t 时间内看完某个节目,每次换台需要 1 单位时间,给出对应的乐子程度,求出最多的乐子度,而且只能看一个节目。
思路:我们预处理一下看完每个节目的最小时间,最小时间是节目时长+换台次数,然后O(n)贪心一次即可。

#include 
#define ll long long
#define ls (id << 1)
#define rs (id << 1 | 1)
using namespace std;
const int N = 105;
int a[N], b[N];
void solve() {
	int n, t;
	cin >> n >> t;
	for (int i = 1; i <= n; i++) cin >> a[i], a[i] += i - 1;
	for (int i = 1; i <= n; i++) cin >> b[i];
	int ans = -1, p = -1;
	for (int i = 1; i <= n; i++) {
		if (a[i] <= t && b[i] > ans) {
			ans = b[i];
			p = i;
		}
	} 
	cout << p << '\n';
}
int main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	int T = 1;
	cin >> T;
	while (T--) {
		solve();
	}
	return 0;
} 

B. Karina and Array(排序,贪心)

题意:给你一个数组有正有负,要求取出两个数使得乘积最大。
思路:如果全正,取最大两个。如果全负,取最小两个。如果有正有负如果n=2,可以称为是最大两个或者最小两个。如果n > 3,我们肯定取同号的,负数取两个最小的,正数取两个最大的,所以我们只需排序之后,取最大或者最小两个。

#include 
#define ll long long
#define ls (id << 1)
#define rs (id << 1 | 1)
using namespace std;
const int N = 2e5 + 5;
int a[N];
void solve() {
	int n;
	cin >> n;
	for (int i = 1; i <= n; i++) cin >> a[i];
	sort(a + 1, a + 1 + n);
	cout << max(1ll * a[1] * a[2], 1ll * a[n] * a[n - 1]) << '\n';
}
int main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	int T = 1;
	cin >> T;
	while (T--) {
		solve();
	}
	return 0;
} 

C. Bun Lover(观察,找规律)

题意:给你一个圈圈饼,要求求出巧克力边的总长度。
思路:观察发现,边长大致符合1 + 2 + … + n 我们可以发现答案。

#include 
#define ll long long
#define ls (id << 1)
#define rs (id << 1 | 1)
using namespace std;
const int N = 2e5 + 5;
void solve() {
	ll n;
	cin >> n;
	cout << n * (n + 1) + n + 2 << '\n';
}
int main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	int T = 1;
	cin >> T;
	while (T--) {
		solve();
	}
	return 0;
} 

D. Super-Permutation(数学、找规律)

题意:找到一个排列a使得,bi = (a1 + a2 + … + ai) % n + 1 也是一个排列。
思路:首先考虑什么时候是无解的,当n>1时,且为奇数的时候, bn = n * (n + 1) / 2 % n + 1 = 1,首先如果说ak=n, k必须是在1处的,因为如果 k > 1的话, bk = (bk-1 + ak) = bk-1不符合排列的定义。所以a1 = n, b1 = 1 = bn矛盾。观察发现,样例的余数是 0 5 1 4 2 3。我们模仿构造,观察发现,大余数是依次 5 4 3,前一个r > 上一个mod 可以直接作差得到。小余数的话,我们发现和上一个余数恰好互补,

#include 
#define ll long long
#define ls (id << 1)
#define rs (id << 1 | 1)
using namespace std;
const int N = 2e5 + 5;
int a[N];
void solve() {
	int n;
	cin >> n;
	if (n == 1) {
		cout << 1 << '\n';
		return;
	}
	if (n & 1) {
		cout << -1 << '\n';
		return;
	}
	a[1] = n;  //0 n - 1 1 
	int mod = 0, r = n;//0 5 1 4 2 3
	for (int i = 2; i <= n; i++) {
		if (i % 2 == 0) r--, a[i] = r - mod;
		else a[i] = n + 1 - a[i - 1];
		mod = (mod + a[i]) % n;
	}
	for (int i = 1; i <= n; i++) cout << a[i] << " \n"[i == n];
}
int main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	int T = 1;
	cin >> T;
	while (T--) {
		solve();
	}
	return 0;
} 

E. Making Anti-Palindromes(字符串,贪心)

题意:通过交换字母,使得字符串对称位置均不相同,求出最小操作次数。
思路:首先考虑无解的情况,有两种,一种奇数的情况,中间处必然对称相等,另一种是某种字符数超过一半的情况。其余必定有解。那么如何最少操作,首先我们统计每种字符的对称相同的情况。如果说我们有两个不同的对称相等的字符对,只需交换一次,便可以减少两个相同对,这是最好的情况,一换二。但是,如果说某种字符对的数量过多,那么必须和其他本来就两侧不同的字符对单独交换,一换一。

#include 
#define ll long long
#define ls (id << 1)
#define rs (id << 1 | 1)
using namespace std;
const int N = 2e5 + 5;
int cnt[26], cp[26];
void solve() {
	int n;
	cin >> n;
	for (int i = 0; i < 26; i++) cnt[i] = cp[i] = 0;
	string s;
	cin >> s;
	if (n & 1) {
		cout << -1 << '\n';
		return;
	}
	for (auto x : s) {
		cnt[x - 'a']++;
	}
	for (int i = 0; i < 26; i++) {
		if (cnt[i] > n - cnt[i]) {
			cout << -1 << '\n';
			return;
		}
	}
	int l = 0, r = n - 1, all = 0;
	while (l < r) {
		if (s[l] == s[r]) cp[s[l] - 'a']++, all++;
		l++, r--;
	}
	int ans = 0, over = 0;
	for (int i = 0; i < 26; i++) {
		if (cp[i] > all - cp[i]) over += 2 * cp[i] - all;
	}
	cout << (all - over + 1) / 2 + over << '\n';
}
int main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	int T = 1;
	cin >> T;
	while (T--) {
		solve();
	}
	return 0;
} 

F. Gardening Friends(树的直径)

引理:一个点的最远点必然是树的直径的一端。
证明:(图略)假设直径两端为s,t。对于某个点x,其最远点为y,为直径外一点(在直径上很容易证明)。1、如果说x->y与s->t没有交点,必定有一个路径s->x(树的联通性)那么|x->y| > |x->s->t| 与树的直径矛盾。2、如果x->y与s->t有交点,记作o,|s->o| < |s->x->o|, |t->o| < |t->y->o|,|s->t| < |s->x->y->t|,矛盾。
题意:(可恶啊我读错成所有距离之和了)定义value为根到某个最深的点的 * k, 移动根一个单位花费c,求出最大的剩余价值。
思路:先找到直径一端,搜出该点为根的各点深度,再得到另一端,搜出各点深度,两者取大便是最远点的深度。
碎碎念:指针的使用可以让dfs复用,可以减少代码量。

#include 
#define ll long long
#define ls (id << 1)
#define rs (id << 1 | 1)
using namespace std;
const int N = 2e5 + 5;
vector<int> g[N];
int d[N], d1[N], d2[N];
void dfs(int u, int fa, int *d) {
	d[u] = d[fa] + 1;
	for (auto v : g[u]) {
		if (v == fa) continue;
		dfs(v, u, d);
	}
}
void solve() {
	int n, k, c;
	cin >> n >> k >> c;
	for (int i = 1; i <= n; i++) g[i].clear();
	for (int i = 1; i < n; i++) {
		int u, v;
		cin >> u >> v;
		g[u].push_back(v);
		g[v].push_back(u);
	}
	dfs(1, 0, d);
	int maxn = 0, s;
	for (int i = 1; i <= n; i++) {
		if (d[i] > maxn) maxn = d[i], s = i;
	}
	dfs(s, 0, d1);
	maxn = 0;
	for (int i = 1; i <= n; i++) {
		if (d1[i] > maxn) maxn = d1[i], s = i;
	}
	dfs(s, 0, d2);
	ll ans = -1e18;
	for (int i = 1; i <= n; i++) {
		ans = max(ans, 1ll * max(d1[i] - 1, d2[i] - 1) * k - 1ll * (d[i] - 1) * c);
	}
	cout << ans << '\n';
}
int main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	int T = 1;
	cin >> T;
	while (T--) {
		solve();
	}
	return 0;
} 

G1. Magic Triples (Easy Version)[暴力、数学]

题意:给出2e5个数,数的范围是1~1e6。
思路:考虑根号分治,枚举平方因子,枚举每一个ak,时间复杂度为O(nsqrt(a)+K),K是复杂度包含map的log,1e6的因子个数240左右,估计可得总运算次数不超过4e8时限4s绰绰有余。

#include 
#define ll long long
#define ls (id << 1)
#define rs (id << 1 | 1)
using namespace std;
const int N = 2e5 + 5;
int a[N];
void solve() {
	map<int, int> mp;
	int n;
	cin >> n;
	for (int i = 1; i <= n; i++) {
		cin >> a[i];
		mp[a[i]]++;
	}
	ll ans = 0;
	for (int i = 1; i <= n; i++) {
		int cnt = mp[a[i]];
		if (cnt >= 3) ans += 1ll * (cnt - 1) * (cnt - 2);
		for (int j = 2; j * j <= a[i]; j++) {
			if (a[i] % (j * j) == 0) {
				ans += 1ll * mp[a[i] / j] * mp[a[i] / j / j];
			}
		} 
	}
	cout << ans << '\n';
}
int main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	int T = 1;
	cin >> T;
	while (T--) {
		solve();
	}
	return 0;
} 

G2 - Magic Triples (Hard Version) [值域分治]

题意:a的范围为1e9,其余相同
思路:考虑值域分治,这里我们枚举的是等比数列的中间项aj,当aj <= 1e6的时候,我们可以直接枚举因子b,根号分治;当a>1e6的时候,b一定不超过1000,直接枚举b即可。时间复杂度为O(na^(0.333)logn + K) 这题容易卡常,用unmap由于种子已知会被人构造恰好次次哈希的冲突的数据(哈希表内部有链表的结构,发现哈希冲突的时候会调用链表),从而unmap退化为n2复杂度,不可接受,可以使用手写哈希,手写哈希也不一定快多少,因为自己造的种子,还是容易发生哈希冲突,常数也不小。这里记忆了数组,因为考虑到这里是捆绑测试,可能出现强的数据点*t的情况(test55),注意这里必须要用count判断是否存在,因为如果不存在会开辟新的空间,从而增加map的深度,大幅度提高常数(tle4)。

#include 
#define ll long long
#define ls (id << 1)
#define rs (id << 1 | 1)
using namespace std;
const int N = 2e5 + 5;
ll a[N];
map<vector<int>, int> trick;
void solve() {
	map<ll, int> mp;
	int n;
	cin >> n;
	vector<int> tag;
	for (int i = 1; i <= n; i++) cin >> a[i], mp[a[i]]++, tag.push_back(a[i]);
	ll ans = 0;
	if (trick.count(tag)) {
		cout << trick[tag] << '\n';
		return;
	}
	for (int i = 1; i <= n; i++) {
		int cnt = mp[a[i]];
		if (cnt >= 3) ans += 1ll * (cnt - 1) * (cnt - 2);
	}
	for (int i = 1; i <= n; i++) {
		if (a[i] <= 1000000) {
			for (int j = 2; j * j <= a[i]; j++) {
				if (a[i] % j == 0) {
					if (mp.count(a[i] / j) && mp.count(a[i] * j)) ans += 1ll * mp[a[i] / j] * mp[a[i] * j];
					if (a[i] != 1ll * j * j && mp.count(j) && mp.count(a[i] * (a[i] / j))) {
						ans += mp[j] * mp[a[i] * (a[i] / j)];
					}
				}
			}
			if (a[i] > 1 && mp.count(1) && mp.count(a[i] * a[i])) {
				ans += mp[1] * mp[a[i] * a[i]];
			}
		} else {
			for (int b = 2; b <= 1000; b++) {
				if (a[i] % b == 0 && mp.count(a[i] / b) && mp.count(a[i] * b)) {
					ans += 1ll * mp[a[i] / b] * mp[a[i] * b];
				}
			}
		}
	}
	cout << ans << '\n';
	trick[tag] = ans;
}
int main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	int T = 1;
	cin >> T;
	while (T--) {
		solve();
	}
	return 0;
} 

你可能感兴趣的:(练习题,算法,贪心算法)