Codeforces 1660 Codeforces Round #780 (Div. 3)-AK题解

题目链接

A

题意

你现在有a个一元硬币和b个两元硬币,问你最小不能拼凑出来的钱数是多少?

思路

  • 如果没有一元硬币,那么答案就是1
  • 否则答案就是 a + b ∗ 2 + 1 a+b*2+1 a+b2+1

AC代码

#include <bits/stdc++.h>

using namespace std;
using ll = long long;

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

	int t;
	cin >> t;
	while(t--) {
		int a, b;
		cin >> a >> b;
		if(a == 0) cout << 1 << '\n';
		else cout << a + b * 2 + 1 << '\n';
	}

    return 0;
}

B

题意

你有n包糖果,每次你可以从数量最多的糖果包里选择一个糖果吃掉,不能连续吃相同的糖果,问是否可以将所有糖果都吃完?

思路

  • 如果只有一堆
    • 如果这堆只有一个糖果:YES
    • 否则:NO
  • 否则:排序
    • 如果最大数量的那一包和次大的那包相差不超过1:YES(因为可以一直这样削弱数量最多的,直到将全部都削弱掉)
    • 否则:NO

AC代码

#include <bits/stdc++.h>

using namespace std;
using ll = long long;

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

	int t;
	cin >> t;
	while(t--) {
		int n;
		int ok = 0;
		cin >> n;
		vector<int> a(n + 1);
		for(int i = 1; i <= n; i++) cin >> a[i];
		sort(a.begin() + 1, a.end());
		if(n == 1 && a[n] == 1) ok = 1;
		else if((a[n] == a[n - 1] || a[n] == a[n - 1] + 1)) ok = 1;
		if(ok) cout << "YES" << '\n';
		else cout << "NO" << '\n';
	}

    return 0;
}

C

题意

定义一个字符串满足以下条件称为偶字符串

  • 该字符串的长度是偶数
  • 对于所有的奇数下标 i ( 1 ≤ i ≤ n − 1 ) , a [ i ] = a [ i + 1 ] i(1\leq{i}\leq{n-1}),a[i]=a[i+1] i(1in1),a[i]=a[i+1]

你每次操作可以从字符串中删除一个字符,问最少的操作次数使得该字符串是偶字符串

思路

  • 贪心
  • 从前向后遍历,直到遇到过一种字符出现过两次,保留出现过两次的这个字符,其他的全都删掉,然后重复这个过程直到结束。

AC代码

#include <bits/stdc++.h>

using namespace std;
using ll = long long;

const int maxn = 2e5 + 6;
char s[maxn];

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

	int t;
	cin >> t;
	while(t--) {
		cin >> s + 1;
		int n = strlen(s + 1), ans = 0;
		bitset<26> cnt(0);
		for(int i = 1; i <= n; i++) {
			if(cnt[s[i] - 'a'] == 0) cnt[s[i] - 'a'] = 1, ans++;
			else {
				ans--;
				cnt.reset();
			}
		}
		cout << ans << '\n';
	}

	return 0;
}

D

题意

你有一个数组,数组的元素 a i a_i ai满足 − 2 ≤ a i ≤ 2 -2\leq{a_i}\leq2 2ai2,你可以从数组的头部和尾部删除若干个元素,问分别从数组头部和尾部分别删除多少个元素可以使得该数组的元素乘积最大

  • 注:数组为空的时候,该数组的乘积为1

思路

  • 保留下来的数组是不能包含有 0 0 0的,所以我们可以用 0 0 0分割该数组,对于每一段分开处理
  • 子段的值最大,说明该子段有很多的“2”,所以我们要保证得到的子段里 2 2 2尽可能的多
    • 如果该子段中负数的数量是偶数个,那么可以不删
    • 否则我们就可以只从一边删元素,直到删到一个负数就停下。

实现方法:

tuple<int, int, int> ans = {-1, 0, 0};
get<0>(ans); //得到ans的第一个元素

保留的三个数分别是区间 ∣ a i ∣ = 2 |a_i|=2 ai=2的数的数量、区间的左端点、区间的右端点,由于 t u p l e tuple tuple已经重载了比较运算符,比较的时候第一个元素的优先级最高,然后是第二个元素……

  • 通过实现pre函数得到只删子段前边元素的最优解
  • 通过实现suf函数得到只删子段后边元素的最优解

AC代码

#include <bits/stdc++.h>

using namespace std;
using ll = long long;

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

	int t;
	cin >> t;
	while(t--) {
		int n;
		cin >> n;
		vector<int> a(n + 1), ve(1, 0);
		tuple<int, int, int> ans = {-1, 0, 0};
		for(int i = 1; i <= n; i++) {
			cin >> a[i];
			if(a[i] == 0) ve.push_back(i);
		}
		ve.push_back(n + 1);
		auto pre = [&](int l, int r) -> tuple<int, int, int> {
			tuple<int, int, int> ret = {0, l, r};
			int fu = 0;
			for(int i = l; i <= r; i++) {
				get<0>(ret) += (abs(a[i]) == 2);
				fu += (a[i] < 0);
			}
			if(fu & 1) {
				for(int i = l; i <= r; i++) {
					if(abs(a[i]) == 2) get<0>(ret) -= 1;
					get<1>(ret)++;
					if(a[i] < 0) break;
				}
			}
			return ret;
		};
		auto suf = [&](int l, int r) -> tuple<int, int, int> {
			tuple<int, int, int> ret = {0, l, r};
			int fu = 0;
			for(int i = l; i <= r; i++) {
				get<0>(ret) += (abs(a[i]) == 2);
				fu += (a[i] < 0);
			}
			if(fu & 1) {
				for(int i = r; i >= l; i--) {
					if(abs(a[i]) == 2) get<0>(ret) -= 1;
					get<2>(ret)--;
					if(a[i] < 0) break;
				}
			}
			return ret;
		};
		for(int i = 1; i < ve.size(); i++) {
			ans = max(ans, pre(ve[i - 1] + 1, ve[i] - 1));
			ans = max(ans, suf(ve[i - 1] + 1, ve[i] - 1));
		}
		cout << get<1>(ans) - 1 << ' ' << n - get<2>(ans) << '\n';
	}

    return 0;
}

E

题意

给出一个n*n的01矩阵,给出以下几种操作

  • 花费一个树脂,使该矩阵中的一个数字异或1
  • 花费零个树脂,使该矩阵循环左移一次,即对于每一个 2 ≤ i ≤ n 2\leq{i}\leq{n} 2in,用第 i i i列替换第 i − 1 i-1 i1列,用第1列替换第 n n n列。
  • 花费零个树脂,使该矩阵循环右移一次,即对于每一个 1 ≤ i ≤ n − 1 1\leq{i}\leq{n-1} 1in1,用第 i i i列替换第 i + 1 i+1 i+1列,用第n列替换第 1 1 1列。
  • 花费零个树脂,使该矩阵循环上移一次,即对于每一个 2 ≤ i ≤ n 2\leq{i}\leq{n} 2in,用第 i i i行替换第 i − 1 i-1 i1行,用第1行替换第 n n n行。
  • 花费零个树脂,使该矩阵循环下移一次,即对于每一个 1 ≤ i ≤ n − 1 1\leq{i}\leq{n-1} 1in1,用第 i i i行替换第 i + 1 i+1 i+1行,用第n行替换第 1 1 1行。

问最少需要消耗多少树脂,使得该矩阵可以变成一个单位矩阵,即主对角线上的值为1,其他位置的元素为0。

思路

不难发现,对这个数组只需要一种循环移位操作就可以得到最优的答案,因为主对角线上的值不管选用哪种循环移位方法,在n次循环移位过后都会找到最优解。所以将行数扩大一倍,将该01矩阵赋值一边,求一个斜缀和(记录这个点的左上方有多少个0),即可在 O ( n 2 ) O(n^2) O(n2)的复杂度下得到最优答案。

AC代码

#include <bits/stdc++.h>

using namespace std;
using ll = long long;

const int maxn = 2e3 + 5;
char s[maxn << 1][maxn];
int pre[maxn << 1][maxn];

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

	int t;
	cin >> t;
	while(t--) {
		int n, t0 = 0, t1 = 0;
		cin >> n;
		for(int i = 1; i <= n; i++) {
			cin >> s[i] + 1;
		}
		for(int i = n + 1; i <= n << 1; i++) {
			for(int j = 1; j <= n; j++) {
				s[i][j] = s[i - n][j];
				t0 += (s[i][j] == '0');
			}
		}
		t1 = n * n - t0;
		for(int i = 1; i <= n << 1; i++) {
			for(int j = 1; j <= n; j++) {
				pre[i][j] = pre[i - 1][j - 1] + (s[i][j] == '0');
			}
		}
		int ans = 0x3f3f3f3f;
		for(int i = n + 1; i <= n << 1; i++) {
			ans = min(ans, pre[i][n] + t1 - (n - pre[i][n]));
		}
		cout << ans << '\n';
	}

    return 0;
}

F1

题意

给出一个只包含 + , − +,- +, 的字符串,给出以下的操作

  • 两个相邻的减号可以变成一个加号,即 + − − − − +---- + > + + − − ++-- ++ or + − + − +-+- ++ or + − − + +--+ ++

定义字符串是有希望变成好的,当且仅当这个字符串通过若干次操作后使得加号的数量等于减号的数量。

问给定的字符串中有多少字串有希望变成好的

思路

两个减号可以被替换为一个加号,如果我们定义一个前缀和,减号代表减一,加号代表加一,通过一次操作过后字符串的值比原来多了3

  • 如何确定一个串中是否存在两个连续的减号呢?如果该字串的值是小于-2的,那么必然存在(细品)
  • 如何确定串是有希望变好呢?当且仅当串的值小于等于零,并且串的值对三取余的值为零。
  • F1数据范围较小, O ( n 2 ) O(n^2) O(n2)暴力即可;

AC代码

#include <bits/stdc++.h>

using namespace std;
using ll = long long;

const int maxn = 3e3 + 5;
char s[maxn];

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

	int t;
	cin >> t;
	while(t--) {
		int n, ans = 0;
		cin >> n >> s + 1;
		vector<int> pre(n + 1);
		for(int i = 1; i <= n; i++) {
			if(s[i] == '-') pre[i] = pre[i - 1] - 1;
			if(s[i] == '+') pre[i] = pre[i - 1] + 1;
		}
		for(int i = 1; i <= n; i++) {
			for(int j = i + 1; j <= n; j++) {
				int sum = pre[j] - pre[i - 1];
				if(sum % 3 == 0 && sum <= 0) {
					ans += 1;
				}
			}
		}
		cout << ans << '\n';
	}

    return 0;
}

F2

题意

见F1,数据范围扩大至2e5.

思路

  • 很显然 O ( n 2 ) O(n^2) O(n2)的复杂度不能满足本题要求,那么什么样的子串才符合模三余零呢,我们把前缀和值为0 3 6 9……、前缀和值为1 4 7 10……、前缀和值为2 5 8 11……的这些区间单独拿出来考虑,因为前缀和上两个数做差可以得到一个子串,所以我们可以利用类似于树状数组求逆序对的方法,开三个树状数组,对于每个位置都算一个贡献,这个贡献就算比当前值小的并且和当前数同余的值的数量,用树状数组可以很简单的维护,复杂度 O ( n l o g n ) O(nlogn) O(nlogn).

AC代码

#include <bits/stdc++.h>

using namespace std;
using ll = long long;

const int maxn = 2e5 + 7;
char s[maxn];

template<typename T>
struct Fenwick {
	int n;
	vector<T> ft;
	Fenwick(int n_ = 0) : n(n_), ft(n_ + 1) {}
	void add(int i, int val) {
		while (i < n) {
			ft[i] += val;
			i |= i + 1;
		}
	}
	T query(int i) {
		T x = 0;
		while (i >= 0) {
			x += ft[i];
			i &= i + 1, i--;
		}
		return x;
	}
};

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

	int t;
	cin >> t;
	while(t--) {
		int n;
		ll ans = 0;
		cin >> n >> s + 1;
		vector<int> pre(n + 1, 0);
		for(int i = 1; i <= n; i++) {
			if(s[i] == '+') pre[i] = pre[i - 1] - 1;	
			if(s[i] == '-') pre[i] = pre[i - 1] + 1;	
		}
		vector<Fenwick<int>> bit(3, Fenwick<int>(n * 2 + 1));
		for(int i = 0; i <= n; i++) {
			ans += bit[(pre[i] % 3 + 3) % 3].query(pre[i] + n);
			bit[(pre[i] % 3 + 3) % 3].add(pre[i] + n, 1);
		}
		cout << ans << '\n';
	}
	return 0;
}

结语

喜欢算法竞赛,喜欢它带给我的快乐,乐亦在其中矣。希望可以在昆明取得好成绩。

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值