Codeforces Round #710 (Div. 3) 题解 A~F

Codeforces Round #710 (Div. 3) 题解 A~F

(为什么只有A~F,因为G我不会)

A. Strange Table

题意

给出一个 n × m n\times m n×m矩阵,按列从小到大标号,再将该矩阵从小到大按行标号,求在按列标号时,序号 x x x的元素,在按行标号时序号为多少
1 4 7 10 13 2 5 8 11 14 3 6 9 12 15   ⇒   1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 \begin{matrix} 1 & 4 & 7 & 10 & 13\\ 2 & 5 & 8 & 11 & 14\\ 3 & 6 & 9 & 12 & 15 \end{matrix} \ \Rightarrow\ \begin{matrix} 1 & 2 & 3 & 4 & 5\\ 6 & 7 & 8 & 9 & 10\\ 11 & 12 & 13 & 14 & 15 \end{matrix} 123456789101112131415  161127123813491451015

思路

根据矩阵的大小,由 x x x可以推出其在矩阵中的坐标,进而根据逆转的坐标推得新的序号

时间复杂度

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

AC代码
ProblemLangVerdictTimeMemory
A - Strange TableGNU C++17Accepted92 ms0 KB
#include <bits/stdc++.h>
using namespace std;

int main() {
//	freopen("in.txt", "r", stdin);
	long long t, n, m, p, x, y;
	cin >> t;
	while (t--) {
		cin >> n >> m >> p;
		x = (p - 1) / n;//位于x行
		y = (p - 1) % n + 1;//位于y列
		cout << m*(y - 1) + x + 1 << endl;
	}
	return 0;
}

B. Partial Replacement

题意

给定一个由’.‘和’*‘组成的长度为 n n n字符串,将其中的某些’*‘用’x’替代,且要求头尾的’*‘必须被替代为’x’,且两个’x’之间的距离(下标之差)不得超过给定的 k k k

思路

典型的贪心问题,从第一个’*‘开始,将其替换为’x’,然后在其后不多于 k k k的长度内寻找最后一个’*’,将这个’*‘替换为’x’,再进行下一次查找,直到找到最后一个’*’

时间复杂度

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

AC代码
ProblemLangVerdictTimeMemory
B - Partial ReplacementGNU C++17Accepted31 ms0 KB
#include <bits/stdc++.h>
using namespace std;

int main() {
//	freopen("in.txt", "r", stdin);
	int t, n, k, i, j, tmp, l, r, cnt;
	string s;
	cin >> t;
	while (t--) {
		cin >> n >> k;
		cin >> s;
		cnt = 0;
		l = 0;
		while (s[l] != '*')
			++l;
		r = n - 1;
		while (s[r] != '*')
			--r;
		tmp = j = i = l;
		++cnt;
		s[i] = 'x';
		while (j <= r) {
			while (j - i <= k && j <= r) {
				if (s[j] == '*')
					tmp = j;//tmp保存最后一个'*'
				++j;
			}
			i = tmp;
			if (s[tmp] != 'x') {//考虑到最后一个'*'已经被替换为'x'的情况,需要判断一下
				s[tmp] = 'x';
				++cnt;
			}
		}
		cout << cnt << endl;
	}
	return 0;
}

C. Double-ended Strings

题意

给定两个字符串 a a a b b b,每次操作允许在字符串的头尾删除一个字符(只要字符串非空),求能使得两个字符串相等的最小操作数

思路

操作后得到的新字符串一定是原字符串连续的一部分,而新字符串相等意味着原字符串中存在一段连续的字符是相等的,问题转化为求原字符串中最长的公共连续段,需要的操作数为总字符数减去两倍的最长公共连续段的长度( ∣ s a ∣ + ∣ s b ∣ − 2 × ∣ s p u b l i c ∣ |s_a|+|s_b|-2\times|s_{public}| sa+sb2×spublic)。此时我们可以采用移动字符串的方法,例如原字符串为 a   b   c   d b   c        \begin{matrix}a\ b\ c\ d\\b\ c\ \ \ \ \ \ \end{matrix} a b c db c      ,此时直接一一比较,发现 a ≠ b a\not= b a=b b ≠ c b\not= c b=c,没有相同的段(最长公共连续段长度为0)。我们换一种对齐方式,可以变成 a   b   c   d b   c \begin{matrix}a\ b\ c\ d\\b\ c\end{matrix} a b c db c,此时b和c是相同的,此时最长公共段长度为2。枚举所有可能的对齐方式,我们一定能找到最大的最长公共段长度,也就得到了最小操作数。需要注意的是,两个字符串在对齐时不一定满足包含关系,即存在 a   b   c              c   d   e \begin{matrix}a\ b\ c\\\ \ \ \ \ \ \ \ \ \ \ \ c\ d\ e\end{matrix} a b c            c d e这样的对齐方式

时间复杂度

O ( n 2 ) O(n^2) O(n2)

AC代码
ProblemLangVerdictTimeMemory
C - Double-ended StringsGNU C++17Accepted15 ms0 KB
#include <bits/stdc++.h>
using namespace std;

int main() {
//	freopen("in.txt", "r", stdin);
	int t, i, j, k, maxm, res, cnt;
	string s1, s2;
	cin >> t;
	while (t--) {
		cin >> s1;
		cin >> s2;
		maxm = 0;//用于保存所有对齐方式中最大的最长公共连续段长度
		i = 0;//s1固定不动,s2移动
		for (j = 0; j < s2.length(); ++j) {
			res = 0;//用于保存当前对齐方式下最长公共连续段
			cnt = 0;//用于计数当前位置的公共连续段
			for (k = 0; k + i < s1.length() && k + j < s2.length(); ++k) {
				if (s1[k + i] != s2[k + j])
					cnt = 0;
				else
					++cnt;
				res = max(res, cnt);
			}
			maxm = max(maxm, res);
		}
		j = 0;//s2固定不动,s1移动
		for (i = 0; i < s1.length(); ++i) {
			res = 0;
			cnt = 0;
			for (k = 0; k + i < s1.length() && k + j < s2.length(); ++k) {
				if (s1[k + i] != s2[k + j])
					cnt = 0;
				else
					++cnt;
				res = max(res, cnt);
			}
			maxm = max(maxm, res);
		}
		cout << s1.length() + s2.length() - 2 * maxm << endl;
	}
	return 0;
}

D. Epic Transformation

题意

给定一个长为 n n n数组 a a a,现允许对 a a a进行操作,每次选定 a a a中两个不同的元素,并将它们删除,操作次数不限,求操作结束后数组 a a a剩余的元素数的最小值

思路

我们只需关注数组 a a a中出现次数最多的元素(假设其出现次数记作 m m m)。如果 m ≥ n 2 m\ge\frac{n}{2} m2n,则说明该元素最终会剩余,最小剩余数为 m − ( n − m ) m-(n-m) m(nm),化简得 2 × m − n 2\times m-n 2×mn。如果 m < n 2 m<\frac{n}{2} m<2n,那么该元素可以被删除完,由于每次必须删除两个元素,当 n n n为奇数时,一定会有一个元素剩余,因此只需输出 n n n的奇偶性即可,即 n % 2 n\%2 n%2

时间复杂度

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

AC代码
ProblemLangVerdictTimeMemory
D - Epic TransformationGNU C++17Accepted358 ms3300 KB
#include <bits/stdc++.h>
using namespace std;
map<int, int> m;

int main() {
//	freopen("in.txt", "r", stdin);
	int t, n, i, j, k, maxm;
	cin >> t;
	while (t--) {
		cin >> n;
		m.clear();
		for (i = 0; i < n; ++i) {
			cin >> j;
			++m[j];//利用map进行数组元素出现次数的统计
		}
		maxm = 0;
		for (auto it : m) {
			if (it.second > maxm)
				maxm = it.second;//遍历查找最大出现次数
		}
		if (maxm <= n - maxm)
			cout << n % 2 << endl;
		else
			cout << 2 * maxm - n << endl;
	}
	return 0;
}

E. Restoring the Permutation

题意

定义数列 q q q为排列 p p p的前缀最大值,也就是说, q i = m a x ( p 1 , p 2 , … , p i ) q_i=max(p_1,p_2,\dots,p_i) qi=max(p1,p2,,pi)。现给出数列 q q q,求可能的排列 p p p中字典序最大和最小的项

思路

首先可以肯定,当 q i ≠ q i − 1 q_i\not=q_{i-1} qi=qi1 i = 1 i=1 i=1时, p i = q i p_i=q_i pi=qi(我称之为定值点)。当 i i i取其他值时,满足 p i < q i p_i<q_i pi<qi(我称之为不定值点)。因此,无论是最小序列还是最大序列,定值点的值已经确定,对于最小序列,只需将其余没有出现过的数从小到大依次填入不定值点即可,而对于最大序列,需要从左到右依次填入小于 q i q_i qi且没有出现过的最大值。最小序列的实现较容易,可以通过数组以 O ( n ) O(n) O(n)复杂度实现,但对于最大序列,如果使用数组遍历来实现,在最恶劣情况下其复杂度会退化到 O ( n 2 ) O(n^2) O(n2),引起超时,正确解法是使用set的upper_bound / lower_bound来寻找有上限的最大值,单次复杂度 O ( l o g 2 n ) O(log_2n) O(log2n),总复杂度 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n),避免了超时。

时间复杂度

O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)

AC代码
ProblemLangVerdictTimeMemory
E - Restoring the PermutationGNU C++17Accepted421 ms15100 KB
#include <bits/stdc++.h>
using namespace std;
int a[200005], b[200005];
int maxm[200005];
set<int> s1, s2;

int main() {
//	freopen("in.txt", "r", stdin);
	int t, n, i, j, k;
	cin >> t;
	while (t--) {
		cin >> n;
		s1.clear();
		s2.clear();
		for (i = 0; i < n; ++i) {
			cin >> maxm[i];
			s1.insert(i + 1);//这边使用了两个set,其实一个set也可以完成
			s2.insert(i + 1);
		}
		i = j = 0;
		while (i < n) {
			while (j < n && maxm[j] == maxm[i])
				++j;
			a[i] = b[i] = maxm[i];//定值点直接赋值
			s1.erase(maxm[i]);
			s2.erase(maxm[i]);//删除已经出现过的元素,这点不能忘记
			auto it1 = s1.begin();
			auto it2 = s2.lower_bound(maxm[i]);//算法核心,找到小于等于目标值的第一个元素
			if (it2 != s2.begin())//如果n=1,这里的--操作会引起RE,需要判定一下
				--it2;
			for (k = i + 1; k < j; ++k) {//不定值点用循环依次求解
				a[k] = *it1;
				++it1;
				s1.erase(a[k]);
				b[k] = *it2;
				--it2;//先移动迭代器再删除元素
				s2.erase(b[k]);//用完也要记得删除
			}
			i = j;
		}
		for (i = 0; i < n; ++i)
			cout << a[i] << ' ';
		cout << endl;
		for (i = 0; i < n; ++i)
			cout << b[i] << ' ';
		cout << endl;
	}
	return 0;
}

F. Triangular Paths

题意

给出一个无限大的图,该图由无限多个点按照金字塔形组成,从上到下第 r r r层有 r r r个点,每一层中从左到右依次标号为 c = 1 ∼ r c=1\sim r c=1r,用 ( r , c ) (r,c) (r,c)来表示第 r r r行第 c c c个点。初始状态下,对于 r + c r+c r+c是偶数的点, ( r , c ) (r,c) (r,c)有单向边连向其左儿子 ( r + 1 , c ) (r+1,c) (r+1,c),而对于 r + c r+c r+c是奇数的点, ( r , c ) (r,c) (r,c)有单向边连向其右儿子 ( r + 1 , c + 1 ) (r+1,c+1) (r+1,c+1)。现允许对任意点进行操作,使得该点改变指向其儿子的单向边的方向(原本连左儿子就改成右儿子,原本连右儿子就改成左儿子)。每次给出 n n n个点,要求从 ( 1 , 1 ) (1,1) (1,1)开始,通过对点进行任意次数操作,找到一条经过所有给定的点的路径,求操作的最小次数,题目保证存在一个合法的路径

思路

首先找规律,初始状态下, r + c r+c r+c为偶数的点(下称偶数点),一定指向一个 r + c r+c r+c为奇数的点(下称奇数点),而奇数点,依然指向奇数点。由于 r + c r+c r+c r − c r-c rc的奇偶性是相同的,且 r > c r>c r>c,因此以下我们研究 r − c r-c rc的性质。如果我们观察从 ( 1 , 1 ) (1,1) (1,1) ( r , 1 ) (r,1) (r,1)的一系列点,不难发现,每往下一层, r − c r-c rc增加1,点的奇偶性改变一次。由于图中都是单向边,因此往下一层后不可能返回,我们需要首先对所有的目标点按照 r r r从小到大排序。对于从 ( r 1 , c 1 ) (r_1,c_1) (r1,c1) ( r 2 , c 2 ) (r_2,c_2) (r2,c2)的移动过程,只要在 [ r 1 − c 1 , r 2 − c 2 ] [r_1-c_1,r_2-c_2] [r1c1,r2c2]区间内存在一个奇数值,就能不经过任何操作,在不改变 r − c r-c rc的情况下增大 c c c的值,此时只需要计算从 r 1 − c 1 r_1-c_1 r1c1 r 2 − c 2 r_2-c_2 r2c2的过程中,经过了多少奇数值,因为在奇数值处需要改变边的方向。若该奇数值不存在,则意味着 r 1 − c 1 = r 2 − c 2 r_1-c_1=r_2-c_2 r1c1=r2c2,且均为偶数,那么在从 r 1 r_1 r1 r 2 r_2 r2的变化过程中,每一次都需要改变边的方向

时间复杂度

O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)

AC代码
ProblemLangVerdictTimeMemory
F - Triangular PathsGNU C++17Accepted685 ms1600 KB
#include <bits/stdc++.h>
using namespace std;

struct pt {
	int x, y;
} p[200005];

int cmp(pt a, pt b) {
	return a.x < b.x;
}

int main() {
//	freopen("in.txt", "r", stdin);
	int t, n, i, j, k, sum;
	p[0] = {1, 1};//开始在(1,1),预先加入
	cin >> t;
	while (t--) {
		cin >> n;
		for (i = 1; i <= n; ++i) {
			cin >> p[i].x;
		}
		for (i = 1; i <= n; ++i) {
			cin >> p[i].y;
		}
		sort(p + 1, p + n + 1, cmp);//先排序
		sum = 0;
		for (i = 1; i <= n; ++i) {
			j = p[i - 1].x - p[i - 1].y;//r1-c1
			k = p[i].x - p[i].y;//r2-c2
			if (j == k) {
				if (j % 2 == 0)//相等且为偶数,每次都要操作(如果是奇数直接为0)
					sum += p[i].x - p[i - 1].x;
			} else {
				if (j % 2 == 0 && k % 2 != 0)//分类讨论计算要经过多少个奇数点
					sum += (k - j) / 2;
				else
					sum += (k - j + 1) / 2;
			}
		}
		cout << sum << endl;
	}
	return 0;
}

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值