Codeforces Round #764 (Div. 3)

比赛相关信息

比赛信息

比赛名称:Codeforces Round #764 (Div. 3)
比赛地址:Codeforces

比赛过程回顾

A B C D E F G
提交次数 1 1 3 0 0 0 0
首次提交时间 00:04:33 00:26:05 00:59:02
首A时间 00:04:33 00:26:05 01:45:47
最终通过数 19279 11931 7753 3499 228 184 242

总体而言,是一场相当有含金量的比赛。


部分题解与小结

A - Plus One on the Subset

小评

非常水的一题,想到了能够很快的完成。码完代码后多测了几个没必要的样例,导致过题时间较晚。

⇔数学规律、⇔入门级(*800)

题意

给定一个序列 \(a[1…n]\) ,每次可以选出一个子序列,使得其中所有的值 \(+1\) 。输出让所有元素的值都相等需要的最小操作次数。

思路

首先应当想到的是,元素的值只增不减——当所有元素都等于最大值时结束操作。其次应该想到的是,选出任意的一个子序列——每次只需将值最小的元素选出并进行操作。再化简,最大的操作次数即为:最小值增长到最大值需要的次数。

AC代码

点击查看代码
void Solve() {
    cin >> n;
    FOR(i, 1, n)  cin >> a[i];
    sort(a + 1, a + 1 + n);
    cout << a[n] - a[1] << endl;
}

错误次数

无。


B - Make AP

小评

比较水的一题,速度较慢的原因在于思考未完全(推导出一个没用,需要考虑较多细节,导致解题速度下降。

⇔数学规律、⇔入门级(*900)

题意

给定三个正整数 \(a,b,c\) ,你可以选择其中的任意一个,将其乘上一个正整数 \(m\) ,使得这三个数字恰好构成一个等差数列。

思路

使用等差数列公式: \(a_{i-1} + a_{i+1} = 2 * a_i\)

核心思路在于,使用公式计算出目标值,判断目标值是否大于 \(0\)(由于原数字大于 \(0\) ,乘数大于 \(0\) ,故目标值一定大于 \(0\) ),判断乘数 \(m\) 是否为正整数(即目标值 \(\%\) 原值是否等于 \(0\) )。

计算公差

核心思路在于,计算出公差后目标值 ,其余同上。

AC代码

点击查看代码
void Solve() {
    int x, y, z;
    cin >> x >> y >> z;
    if(2 * y - z > 0 && (2 * y - z) % x == 0) yes;
    else if((x + z) % 2 == 0 && (x + z) / 2 > 0 && (x + z) / 2 % y == 0) yes;
    else if(2 * y - x > 0 && (2 * y - x) % z == 0) yes;
    else no;
}

错误次数

无。


C - Division by Two and Permutation

小评

思维题,其实难度不是很高。类似这题这样的全排列是否存在问题还有很多,下放给出比对链接。

⇔构造性算法、⇔数学规律、⇔入门级(*1100)

题意

对于给定的序列 \(a[1…n]\) ,一次操作可以使得任意的 \(a_i\) 变成 \(\left \lfloor \frac{a_i}{2} \right \rfloor\) ,求解最终能否使得序列成为 \(1\)\(n\) 的一个全排列。

思路

由于需要得到一个全排列,所以序列的每一个数字都有用。可以考虑两种做法:快排、桶排序计算。

(题解做法)快排

先将序列降序排列,然后建立判断数组 \(used[1…n]\)(用于判断 \(1\)\(n\) 是否有元素匹配)。现在开始遍历 \(a\) 序列——令 \(x = a[i]\) ,只要 \(x \ge n\) 或是 \(used[x] = \tt{false}\) (已有元素匹配),则将 \(x\) 整除 \(2\) 。存在下述两种情况:

  • 得到 \(x = 0\) ,说明 \(a[i]\) 这个元素没有匹配,错误,跳出;
  • 发现 \(used[x] = \tt{true}\) (暂无元素匹配),那么将其置为 \(\tt{false}\)

遍历完成后若未跳出,则正确。

(自己做法)桶排计算

先建立一个桶 \(count[1…n]\) ,用于记录数字的数量。先将读入的数字处理到 \(\le n\) ,然后放入桶中。现在开始降序遍历桶,存在下述两种情况:

  • 如果某个桶为空,说明这个元素没有匹配,错误,跳出。
  • 如果某个桶不为空,留下一个,剩余 \(count[i] - 1\) 个的全部加到 \(count[\left \lfloor \frac{a_i}{2} \right \rfloor]\) 桶中。

遍历完成后若未跳出,则正确。

AC代码1(快排)

点击查看代码
void Solve() {
    cin >> n;
    vector<int> a(n), used(n + 1, true);
    for(auto &it : a) cin >> it;
    sort(a.begin(), a.end(), [] (int x, int y) {
        return x > y;
    });
    for(auto it : a) {
        int x = it;
        while(x > n || used[x] == false) x /= 2;
        if(x > 0) used[x] = false;
        else Ans = false;
    }
    if(Ans == false) no;
    else yes;
}

AC代码2(桶排)

点击查看代码
void Solve() {
    int count[55] = {};
    cin >> n;
    FOR(i, 1, n) {
        cin >> x;
        while(x > n) x /= 2;
        count[x] ++;
    }
    FORD(i, 1, n) {
        if(count[i] == 0) Ans = false;
        count[i / 2] += count[i] - 1;
    }
    if(Ans == false) no;
    else yes;
}

错误次数

(比赛 2 次)思路错误,尝试把一个数字能够变换出的所有数字都记录在桶中,若被使用,则从桶中全部删去。


D - Palindromes Coloring

小评

这是道题面非常恐怖的题目,乍一看涉及到了填色问题和回文子串的判断问题,导致我在卡 C 题不得,打算先做这道题的时候,看了几眼题面就果断放弃了,但实际上这道题的难度不大,与染色问题、回文子串问题关联都不大。所以开题的时候还是需要多观察观察再做决定。

⇔贪心、⇔排序、⇔字符串、⇔提高级(*1400)

题意

给定一串字符串,根据以下要求对字符进行染色:

  • 一共能且只能染 \(k\) 种颜色;
  • 有一些字符可以不染色,但你要尽可能多的染色;
  • 取出相同颜色的字符,经过任意次排列后应当可以形成一个回文子串。

要求使得所有颜色的字符串中最短的那串的长度最长,输出这个长度。

思路

贪心(自己做法):

转化下题目:要求从给定的字符串中取出一些字符,组成 \(k\) 个回文子串。我们首先考虑回文子串的组成:若干偶数对字符 + 至多一个单个字符(如 \(\tt{aacbcaa}\) )。所以,我们先统计给定字符串中各个字符的数量,然后将偶数对平均的分给 \(k\) 个串(对于多余的偶数对,我们将其拆分成单个),再判断单个的字符串能否够每个串分到一个。

二分答案:

AC代码(贪心)

点击查看代码
void Solve() {
	int n, k, count[30] = {}, num1 = 0, num0 = 0;
	char x;
	cin >> n >> k;
	FOR(i, 1, n) {
		cin >> x;
		count[x - 'a' + 1] ++;
	}
	FOR(i, 1, 26) {
		num1 += count[i] % 2;
		num0 += count[i] / 2;
	}
	
	ans = num0 / k * 2; //将偶数对平均的分给 k 个串
	num1 += num0 % k * 2; //多余的拆成单个
	
	cout << ans + bool(num1 / k) << endl;
}

错误次数

(补题 4 次)未将多余的偶数对拆分。


G - MinOr Tree

小评

最后一题,但实际上难度不是很大,属于应该做出来的范围主要考察的是最小生成树的生成原理。

⇔位运算、⇔并查集、⇔图论、⇔贪心、⇔铜牌级(*1900)

题意

给定一张图,要求生成一棵生成树,使得这棵树的边权按位或最小。

思路

由于或运算是有一出一,所以要使得按位或最小,即要让选中边所含的 \(1\) 的数量最少,且位数越低越好。故我们先假设所有位数都是 \(1\) ,再按位数从高到低进行循环,寻找可以置为 \(0\) 的位置——假设某一位可以置为 \(0\) ,那么只可以选择这一位为 \(0\) 的边,我们只需判断(使用并查集),使用这些边能否组成连通图,若能,假设成立,删去未选择的边。

最后剩下的未被删去的边一定能组成一张连通图,且由于按位数从高到低循环,得到的答案一定是最优的。

AC代码1(使用 \(\tt{bitset}\)

点击查看代码
//====================
LL n, num, fa[MAX], m;
bool Ans;
bitset<32> ans;
struct node{
	int u, v;
	bool flag;
	bitset<32> B;
}ver[MAX];
//====================
void Clear() {
	ans = 0; Ans = true;
	ans.set();
}
void Prepare(int n) {//初始化
	FOR(i, 1, n) fa[i] = i;
}
int get(int x) {
	if(x == fa[x]) return x;
	return fa[x] = get(fa[x]);
}
void merge(int x, int y) {
	fa[get(x)] = get(y);
}
void Solve() {
	cin >> n >> m;
	FOR(i, 1, m) {
		cin >> ver[i].u >> ver[i].v >> num;
		ver[i].B = num;
		ver[i].flag = true;
	}
	FORD(T, 0, 31) {
		Ans = true;
		Prepare(n);
		FOR(i, 1, m) {
			if(ver[i].B[T] == 0 && ver[i].flag == true) {
				merge(ver[i].u, ver[i].v);
			}
		}
		FOR(i, 2, n) {
			if(get(i) != get(i - 1)) {
				Ans = false;
			}
		}
		if(Ans == false) continue; //不可以被删除,继续查找
		ans.flip(T);
		FOR(i, 1, m) {
			if(ver[i].B[T] == 1) {
				ver[i].flag = false;
			}
		}
	}
	cout << ans.to_ullong() << endl;
}

AC代码2(使用位运算)

点击查看代码
//====================
LL n, m, num, fa[MAX], ans;
bool Ans;
struct node{
	int v, u, w;
	bool flag;
}ver[MAX];
//====================
void Clear() {
	ans = 0; Ans = true;
	
}
void Prepare(int n) {
	FOR(i, 1, n) fa[i] = i;
}
int get(int x) {
	if(fa[x] == x) return x;
	return fa[x] = get(fa[x]);
}
void merge(int x, int y) {
	fa[get(x)] = get(y);
}
void Solve() {
	cin >> n >> m;
	FOR(i, 1, m) {
		cin >> ver[i].u >> ver[i].v >> ver[i].w;
		ver[i].flag = true;
	}
	FORD(bit, 0, 31) {
		Ans = true;
		Prepare(n);
		int judge = (1LL << bit);
		FOR(i, 1, m) {
			if(ver[i].flag == true && (ver[i].w & judge) == 0) {
				merge(ver[i].u, ver[i].v);
			}
		}
		FOR(i, 2, n) {
			if(get(i) != get(i - 1)) {
				Ans = false;
			}
		}
		if(Ans == false) continue;
		FOR(i, 1, m) {
			if((ver[i].w & judge) != 0) {
				ver[i].flag = false;
			}
		}
	}
	FOR(i, 1, m) {
		if(ver[i].flag == true) {
			ans |= ver[i].w;
		}
	}
	cout << ans << endl;
}

错误次数

(补题,方法1,1 次)先将不符合的边删去后再计算是否能组成连通图,若不能,再恢复——这会导致此前已经确定删除的边被还原。

(补题,方法2,1 次)判断是否被删除的数组( \(\tt{flag} [1…n]\) )初始化错误。


文 / WIDA
2022.01.14 成文
首发于WIDA个人博客,仅供学习讨论


更新日记:
2022.01.14 成文


posted @ 2022-01-14 15:42  hh2048  阅读(84)  评论(0编辑  收藏  举报