IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> Codeforces Round #748 (Div. 3) 题解 完整A~G -> 正文阅读

[数据结构与算法]Codeforces Round #748 (Div. 3) 题解 完整A~G

Codeforces Round #748 (Div. 3) 题解

A. Elections

题意

已知竞选中三个候选人的当前得票数 a , b , c a,b,c a,b,c,现在可以增加任何一个人的得票数,要求对于每个人,计算出能使得其得票数大于其余两人的最少增加票数。

思路

按照题意计算即可,假如要使得 a a a的票数最高,只需让 a a a加上 b , c b,c b,c中票数较高的人与 a a a的票数只差,再加上1即可。需要注意的是,如果某个人的得票数已经大于其余两人,应当输出0,而不是负数。

时间复杂度

O ( 1 ) O(1) O(1)

AC代码

ProblemLangVerdictTimeMemory
A - ElectionsGNU C++17Accepted15 ms0 KB
#include <bits/stdc++.h>

using namespace std;

void solve() {
	int a, b, c;
	scanf("%d%d%d", &a, &b, &c);		//每一个都独立计算
	printf("%d %d %d\n", a > max(b, c) ? 0 : max(b, c) + 1 - a,
		   b > max(a, c) ? 0 : max(a, c) + 1 - b,
		   c > max(a, b) ? 0 : max(a, b) + 1 - c);
}

int main() {
//	freopen("in.txt", "r", stdin);
	int t;
	scanf("%d", &t);
	while (t--) {
		solve();
	}
	return 0;
}

B. Make it Divisible by 25

题意

给定一个十进制正整数,每次可以删除其中的任何一位,如果操作后会产生前导0,则会自动去除前导0。问至少删除多少位后,该数不i变成一个能被25整除的正整数。

思路

被25整除的正整数最大的特点是:十位和个位只有4种情况,即00、25、50、75。因此只需从后往前找,对于每种情况,找到第一个匹配的位置即可。

时间复杂度

O ( n ) O(n) O(n)

AC代码

ProblemLangVerdictTimeMemory
B - Make it Divisible by 25GNU C++17Accepted0 ms0 KB
#include <bits/stdc++.h>

using namespace std;

void solve() {
	char s[22];
	scanf("%s", s);
	int n = strlen(s);
	int a, b, p1, p2;
	p1 = p2 = -1;
	for (int i = n - 1; i >= 0; --i) {		//后两位25和75可以合并一起找
		if (p1 == -1 && s[i] == '5') {
			p1 = i;
			continue;
		}
		if (p1 != -1 && (s[i] == '2' || s[i] == '7')) {
			p2 = i;
			break;
		}
	}
	a = n - 1 - p2 - 1;
	p1 = p2 = -1;
	for (int i = n - 1; i >= 0; --i) {		//后两位00和50可以合并一起找
		if (p1 == -1 && s[i] == '0') {
			p1 = i;
			continue;
		}
		if (p1 != -1 && (s[i] == '0' || s[i] == '5')) {
			p2 = i;
			break;
		}
	}
	b = n - 1 - p2 - 1;
	printf("%d\n", min(a, b));		//题目保证有解,所以不需要担心答案是0或者无解的情况
}

int main() {
//	freopen("in.txt", "r", stdin);
	int t;
	scanf("%d", &t);
	while (t--) {
		solve();
	}
	return 0;
}

C. Save More Mice

题意

在一个坐标轴上,有很多个老鼠,一只猫和一个洞,猫的起始位置是0,洞的位置是 n n n,所有老鼠的起始位置在0和 n n n之间,同一个位置可以有多个老鼠。每一个时刻,你可以将一个老鼠向右移动一个单位(坐标值+1),随后猫将向右移动一个单位,并吃掉这个位置上的所有老鼠。如果老鼠移动到了洞里,就不会再移动,也不会被吃掉。问最多能有多少个老鼠存活。

思路

在任何一个时刻,被选中的老鼠一定存活。在 n n n个时刻后,猫会吃掉所有能吃掉的老鼠。因此,我们只需尽可能多的将老鼠移动到洞里。采取贪心策略,排序后选择离洞最近的尽可能多的老鼠,使得它们与洞的距离之和小于 n n n

时间复杂度

O ( n l o g 2 n ) O(nlog_2n) O(nlog2?n)

AC代码

ProblemLangVerdictTimeMemory
C - Save More MiceGNU C++17Accepted124 ms1600 KB
#include <bits/stdc++.h>

using namespace std;

int a[400005];

void solve() {
	int n, k;
	scanf("%d%d", &n, &k);
	for (int i = 0; i < k; ++i) {
		scanf("%d", &a[i]);
	}
	sort(a, a + k, greater<int>());		//要选择离洞近的,所以从大到小排序
	int ans = 0, s = n;		//ans记录有几个老鼠存活,s记录还剩多少距离
	for (int i = 0; i < k; ++i) {
		if (s > n - a[i]) ++ans, s -= n - a[i];
		else break;
	}
	printf("%d\n", ans);
}

int main() {
//	freopen("in.txt", "r", stdin);
	int t;
	scanf("%d", &t);
	while (t--) {
		solve();
	}
	return 0;
}

D1. All are Same

题意

给出一个序列,要求选定一个正整数 k k k,每次操作可以选择序列中的任何一个元素,并将其减少 k k k,这一操作可以进行任意次数。要求在经过若干操作之后,序列中的每一个元素都相等,求最大的可能的 k k k,若 k k k可以取任意大的值,输出-1。

思路

首先我们可以发现,当序列元素在初始状态下就已经相等时, k k k可以任意大。

对于存在不同值的序列,在若干次操作后,序列中的元素一定可以变成序列最小值。

证明:设最终相等的值为 x x x,序列最小值为 m m m:若 x > m x>m x>m,由于 k > 0 k>0 k>0,序列元素只减不增,则一定存在一项 m ′ ≤ m m'\le m mm,一定不能满足 m ′ = x m'=x m=x;若 x < m x<m x<m,则一定有 x = m ? n ? k , n ∈ N ? x=m-n\cdot k,n\in N^* x=m?n?k,nN?,因此取 x ′ = m x'=m x=m同样符合题意。

对于任意序列元素 a i =? m a_i\not=m ai??=m,当满足 k ∣ ( a i ? m ) k|(a_i-m) k(ai??m)(|表示整除)时,可以经过若干次操作使 a i a_i ai?变为 m m m。为了求出满足所所有序列元素的最大 k k k值,我们只要求出 a i ? m a_i-m ai??m的最大公因数即可。求最大公因数(gcd)可以手写欧几里得算法,也可以直接调用C++库函数__gcd(x,y),该函数在<algorithm>头文件中。

时间复杂度

O ( n l o g 2 a i ) O(nlog_2a_i) O(nlog2?ai?)

AC代码

ProblemLangVerdictTimeMemory
D1 - All are SameGNU C++17Accepted15 ms0 KB
#include <bits/stdc++.h>

using namespace std;

int a[100];

void solve() {
	int n;
	scanf("%d", &n);
	for (int i = 0; i < n; ++i) {
		scanf("%d", &a[i]);
	}
	int minm = a[0], maxm = a[0];		//找到序列最大最小值
	for (int i = 1; i < n; ++i) {
		minm = min(minm, a[i]);
		maxm = max(maxm, a[i]);
	}
	if (minm == maxm) {		//最大最小值相等意味着序列元素完全相同
		puts("-1");
		return;
	}
	int ans = maxm - minm;	//使用最大值-最小值可以避免ans初值为0的问题
	for (int i = 0; i < n; ++i) {
		if (a[i] != minm) ans = __gcd(ans, a[i] - minm);	//求最大公因数
	}
	printf("%d\n", ans);
}

int main() {
//	freopen("in.txt", "r", stdin);
	int t;
	scanf("%d", &t);
	while (t--) {
		solve();
	}
	return 0;
}

D2. Half of Same

题意

给出一个序列,要求选定一个正整数 k k k,每次操作可以选择序列中的任何一个元素,并将其减少 k k k,这一操作可以进行任意次数。要求在经过若干操作之后,序列中的至少一半的元素相等,求最大的可能的 k k k,若 k k k可以取任意大的值,输出-1。

思路

这题和上一题的大致思路是相似的,但是难度大很多。观察发现序列长度 n n n的范围只有40,考虑枚举操作结束后相等元素的值(也就是枚举相当于上一题中最小值的那个元素),设计状态 d p [ i ] dp[i] dp[i],表示在最终有 i i i个相等的数的情况下,可能的最大公因数值的集合,随后找出 i ≥ n 2 i\ge \frac{n}{2} i2n?时的最大值。

时间复杂度

O ( n 4 l o g 2 a i ) / O ( n 4 l o g 2 n l o g 2 a i ) O(n^4log_2a_i)/O(n^4log_2nlog_2a_i) O(n4log2?ai?)/O(n4log2?nlog2?ai?)

AC代码

ProblemLangVerdictTimeMemory
D2 - Half of SameGNU C++17Accepted30 ms200 KB
#include <bits/stdc++.h>

using namespace std;

int a[100];
set<int> dp[100];	//其实是应该用unordered_set的,但当时用了set
map<int, int> mp;

void solve() {
	mp.clear();
	int n;
	scanf("%d", &n);
	for (int i = 0; i < n; ++i) {
		scanf("%d", &a[i]);
		++mp[a[i]];			//统计每个数初始状态下有多少个
	}
	for (auto &it: mp) {
		if (it.second >= n / 2) {		//初始状态下已经满足条件,k可取任何值
			puts("-1");
			return;
		}
	}
	int ans = 0;
	for (int i = 0; i < n; ++i) {		//这层循环枚举最小的数
		for (int j = 0; j < n; ++j) dp[j].clear();
		int num = 0;				//统计原本就等于这个数的元素数量
		for (int j = 0; j < n; ++j) if (a[j] == a[i]) ++num;
		for (int j = 0; j < n; ++j) {
			if (a[j] > a[i]) {		//对于每个可能减小到a[i]的元素都进行处理
				for (int k = n - 1; k > 0; --k) {	//必须从大到小迭代相等元素的个数,进行递推
					for (int l: dp[k]) {		//集合元素的枚举,也就是状态的转移过程
						dp[k + 1].insert(__gcd(l, a[j] - a[i]));	//(unordered_)set可以去重,加速运算
					}
				}
				dp[1].insert(a[j] - a[i]);
			}
		}
		for (int j = n / 2 - num; j <= n; ++j) {	//由于我是set,所以最后一个元素就是最大值,如果用的是unordered_set,就需要遍历所有元素取最大值
			if (!dp[j].empty()) ans = max(ans, *--dp[j].end());
		}
	}
	printf("%d\n", ans);
}

int main() {
//	freopen("in.txt", "r", stdin);
	int t;
	scanf("%d", &t);
	while (t--) {
		solve();
	}
	return 0;
}

E. Gardener and Tree

题意

给出一棵树,每次操作删除树上的所有叶子节点,问经过 k k k次操作后,还剩多少节点。(树是无根树,空树经过操作后还是空树)

关于树和叶子节点,以及下面提到的度的概念,不理解者请自行找资料解决,在此不做说明。

思路

我们维护树上所有节点的度数,除了仅有一个节点的树以外,当且仅当节点的度数为1时,该节点是叶子节点。考虑类似拓扑排序的方法,使用bfs解决本题。初状态下将所有1度节点加入队列,然后处理队列中的所有节点,删除与之相连的边(只有一条),并检查与之相连的另一节点在删边后的度数,如果删边后该节点变为1度节点,则新增叶子节点,加入队列。另外需要记录节点的bfs层数,由于只操作 k k k次,当bfs层数达到 k k k后,就不需要再进行操作。

题目坑点:只有1个节点的情况需要特判!

时间复杂度

O ( n ) O(n) O(n)

AC代码

ProblemLangVerdictTimeMemory
E - Gardener and TreeGNU C++17Accepted467 ms18100 KB
#include <bits/stdc++.h>

using namespace std;

vector<int> g[400005];			//g是邻接表,存储图的信息
int vis[400005], in[400005];	//in记录的是节点的度(我沿用了拓扑排序中入度的写法)

void solve() {
	int n, k;
	scanf("%d%d", &n, &k);
	for (int i = 1; i <= n; ++i) g[i].clear();
	memset(vis, 0, (n + 5) << 2);
	memset(in, 0, (n + 5) << 2);
	int x, y;
	for (int i = 0; i < n - 1; ++i) {
		scanf("%d%d", &x, &y);		//图的读入
		g[x].push_back(y);			//加边
		g[y].push_back(x);
	}
	queue<int> q;
	int ans = 0;		//ans是被删掉的点的数量
	for (int i = 1; i <= n; ++i) {
		in[i] = g[i].size();	//初始化度数
		if (in[i] <= 1) {		//bfs的初始节点集
			vis[i] = 1;
			++ans;
			q.push(i);
		}
	}
	while (!q.empty()) {
		int p = q.front();
		q.pop();
		if (vis[p] >= k) continue;	//剪枝
		for (int i: g[p]) {
			if (!vis[i]) {
				--in[i];		//删边
				if (in[i] == 1) {
					vis[i] = vis[p] + 1;
					++ans;
					q.push(i);
				}
			}
		}
	}
	printf("%d\n", n - ans);		//输出的时候要用总节点数减去删掉的节点数
}

int main() {
//	freopen("in.txt", "r", stdin);
	int t;
	scanf("%d", &t);
	while (t--) {
		solve();
	}
	return 0;
}

F. Red-Black Number

题意

给出一个位数为 n n n的十进制非负整数 x x x(可能有前导0),另给两个正整数 A A A B B B,要求将 x x x的每一位涂成红色或黑色,每种颜色都至少有一位数字,使得同种颜色的数字按原本顺序组合后,红色数字组成的数能被 A A A整除,黑色数字组成的数能被 B B B整除。答案输出使得红色数字个数与黑色数字个数最接近的一种涂色方式,若有多个答案可以输出任意一个,若无解,输出-1。

思路

观察发现 n , A , B n,A,B n,A,B的范围均只有40,考虑 n 4 n^4 n4的四维dp。设计状态 d p [ i ] [ j ] [ k ] [ l ] dp[i][j][k][l] dp[i][j][k][l]:对于前 i i i个数字,红色数字组成的数模 A A A j j j,黑色数字组成的数模 B B B k k k,其中红色数字有 l l l个。初始状态为 d p [ 0 ] [ 0 ] [ 0 ] [ 0 ] dp[0][0][0][0] dp[0][0][0][0]

递推时,将 i i i进行迭代,每次迭代枚举每一个有效的 j , k , l j,k,l j,k,l,并枚举这一位涂的颜色(红或黑),记录操作(涂的颜色)以及来源路径(上一层中的位置),详见代码

完成递推后,枚举红色数字个数与黑色数字个数之差的绝对值,若存在有效状态 d p [ n ] [ 0 ] [ 0 ] [ l ] dp[n][0][0][l] dp[n][0][0][l],则说明该情况成立,通过前序dfs回溯路径,输出答案。

时间复杂度

O ( n 4 ) O(n^4) O(n4)

AC代码

ProblemLangVerdictTimeMemory
F - Red-Black NumberGNU C++17Accepted109 ms22100 KB
#include <bits/stdc++.h>

using namespace std;

int x[50];
pair<int, int> dp[41][41][41][41];		//dp状态,first记录涂红色还是黑色,second记录路径来源
int n, a, b;

void display(int i, int j, int k, int l) {		//前序遍历回溯路径
	if (!i) return;
	if (dp[i][j][k][l].first) display(i - 1, dp[i][j][k][l].second, k, l - 1);
	else display(i - 1, j, dp[i][j][k][l].second, l);
	printf("%c", dp[i][j][k][l].first ? 'R' : 'B');
}

void solve() {
	scanf("%d%d%d", &n, &a, &b);
	for (int i = 0; i < n; ++i) scanf("%1d", &x[i]);	//输入时直接按位分解存储
	memset(dp, -1, sizeof dp);		//清空数组
	dp[0][0][0][0].first = -2;
	for (int i = 0; i < n; ++i) {		//枚举数位
		for (int j = 0; j < a; ++j) {		//枚举红色数的模数
			for (int k = 0; k < b; ++k) {		//枚举黑色数的模数
				for (int l = 0; l <= i; ++l) {		//枚举红色数字个数
					if (dp[i][j][k][l].first != -1) {		//尝试转移
						if (dp[i + 1][(j * 10 + x[i]) % a][k][l + 1].first == -1)	//涂红色
							dp[i + 1][(j * 10 + x[i]) % a][k][l + 1] = make_pair(1, j);
						if (dp[i + 1][j][(k * 10 + x[i]) % b][l].first == -1)		//涂黑色
							dp[i + 1][j][(k * 10 + x[i]) % b][l] = make_pair(0, k);
					}
				}
			}
		}
	}
	for (int i = 0; i <= n / 2; ++i) {		//枚举红黑数字个数差的绝对值
		if (n / 2 + i < n) {
			if (dp[n][0][0][n / 2 + i].first != -1) {
				display(n, 0, 0, n / 2 + i);
				puts("");
				return;
			}
		}
		if (n / 2 - i > 0) {
			if (dp[n][0][0][n / 2 - i].first != -1) {
				display(n, 0, 0, n / 2 - i);
				puts("");
				return;
			}
		}
	}
	printf("-1\n");
}

int main() {
//	freopen("in.txt", "r", stdin);
	int t;
	scanf("%d", &t);
	while (t--) {
		solve();
	}
	return 0;
}

G. Changing Brackets

题意

给出一个字符串 s s s,仅由字符’(’,’)’,’[’,’]‘组成,允许以0的代价翻转括号(也就是’(‘和’)‘互换,’[‘和’]'互换),允许以1的代价将任何形式的圆括号变成方括号,但不允许将方括号变成圆括号。

q q q次询问,每次询问,求对于给定的正整数 l , r l,r l,r,将子串 s [ l . . r ] s[l..r] s[l..r]转换为正则括号序列(括号完全匹配)的最小代价。

思路

Idea by REXWind

首先给出结论:统计奇数位置和偶数位置上方括号的个数,当且仅当子串中奇数位置的方括号数等于偶数位置的方括号数时,序列可以形成正则括号序列。

不难发现,括号的方向不影响答案(因为代价是0)。由于圆括号可以变成方括号,所以对答案造成影响的应当是方括号的位置和数量,圆括号根据方括号的需求进行变换。

假设有两个方括号,都位于奇数位置上(或都位于偶数位置上),那么这两个方括号之间的括号序列不可能形成正则序列。

证明:两个方括号之间的这段序列的长度是奇数,因此方括号和圆括号的个数中一定有一个是奇数,奇数个括号不可能完成匹配。

假设有两对方括号,其中两个在奇数位置,两个在偶数位置:

  • 如果是嵌套关系(例如方括号位置是 1 , 5 , 8 , 10 1,5,8,10 1,5,8,10),如果仅考虑内层两个方括号之间的序列( s [ 5..8 ] s[5..8] s[5..8]),若该序列是正则序列,则整个序列也是正则序列(因为内外层之间的圆括号数( s [ 2..4 ] + s [ 9..9 ] s[2..4]+s[9..9] s[2..4]+s[9..9])是偶数,且中间没有插入单独的方括号,一定可以完成匹配);
  • 如果是并列关系(例如方括号位置是 1 , 4 , 7 , 12 1,4,7,12 1,4,7,12),如果两对方括号之内的序列( s [ 1..4 ] s[1..4] s[1..4] s [ 7..12 ] s[7..12] s[7..12])是正则序列,则整个序列也是正则序列(因为两个正则序列之间的圆括号数( s [ 5..6 ] s[5..6] s[5..6])是偶数,且中间没有插入单独的方括号,一定可以完成匹配)。

使用归纳法可证,如果一个序列中奇数位置的方括号数和偶数位置的圆括号数相等,则序列是正则序列。

回到询问,暴力统计子序列中的奇数和偶数位置方括号数显然不可取,前缀和优化之。

每次询问,对前缀和作差,求出奇偶位置的方括号数,作差取绝对值,即是答案。

时间复杂度

O ( n + q ) O(n+q) O(n+q)

AC代码

ProblemLangVerdictTimeMemory
G - Changing BracketsGNU C++17Accepted140 ms8800 KB
#include <bits/stdc++.h>

using namespace std;

char s[1000005];
int a[2][1000005];		//奇偶位置方括号的前缀和

void solve() {
	scanf("%s", s + 1);
	int n = strlen(s + 1);
	for (int i = 1; i <= n; ++i) {		//预处理前缀和
		if (s[i] == '[' || s[i] == ']') a[i & 1][i] = a[i & 1][i - 1] + 1;
		else a[i & 1][i] = a[i & 1][i - 1];
		a[i & 1 ^ 1][i] = a[i & 1 ^ 1][i - 1];
	}
	int q;
	scanf("%d", &q);
	while (q--) {
		int l, r;
		scanf("%d%d", &l, &r);		//每次询问O(1)输出
		printf("%d\n", abs((a[0][r] - a[0][l - 1]) - (a[1][r] - a[1][l - 1])));
	}
}

int main() {
//	freopen("in.txt", "r", stdin);
	int t;
	scanf("%d", &t);
	while (t--) {
		solve();
	}
	return 0;
}

后记

本FW又双叒叕AK失败了呜呜呜。。

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2021-10-15 12:02:00  更:2021-10-15 12:04:51 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 7:20:56-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码