Codeforces Round #731 (Div. 3) 题解 完整A~G

Codeforces Round #731 (Div. 3) 题解

A. Shortest Path with Obstacle

题意

每次给出3个点A,B,F的坐标,求从A到B不经过F的最短路径长度。(每次只允许上下左右走1格)

思路

先不考虑障碍物,A到B的最短路径长度就是A和B的曼哈顿距离 ( ∣ x A − x b ∣ + ∣ y A − y B ∣ ) (|x_A-x_b|+|y_A-y_B|) (xAxb+yAyB)。现在考虑障碍物,只有A,B在同一x值或y值,且F恰好在A,B之间时,这条最短路径不可行,需要绕行2个单位长度。

时间复杂度

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

AC代码
ProblemLangVerdictTimeMemory
A - Shortest Path with ObstacleGNU C++17Accepted15 ms0 KB
#include <bits/stdc++.h>

using namespace std;

void solve() {
	int x1, y1, x2, y2, x3, y3;
	scanf("%d%d%d%d%d%d", &x1, &y1, &x2, &y2, &x3, &y3);
	int add = 0;
	if (x1 == x2 && x1 == x3 && y3 > min(y1, y2) && y3 < max(y1, y2))add = 2;
	if (y1 == y2 && y1 == y3 && x3 > min(x1, x2) && x3 < max(x1, x2))add = 2;
	printf("%d\n", abs(x1 - x2) + abs(y1 - y2) + add);
}

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

B. Alphabetical Strings

题意

定义一个长度不超过26的字符串 s s s为字典序的(alphabetical),满足条件: s s s由一个空字符串,每次在左端或右端添加一个字母得到,并且添加字母的顺序为字母表顺序 a , b , c , … , z a,b,c,\dots,z a,b,c,,z(也就是说,长度为 l e n len len的字符串包含字母表中前 l e n len len个字母)。现给出一些长度不超过26的字符串,要求判断是不是字典序的。

思路

逆向思维,对于满足条件的字典序的字符串,长度为 l e n len len的时候,在字符串的第一个或最后一个位置,一定能找到字母表中第 l e n len len个字母。因此我们只需要不断地删去最大的字母即可,直到字符串长度为0(满足条件)或字符串两端均无法删去(不满足条件)。

时间复杂度

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

AC代码
ProblemLangVerdictTimeMemory
B - Alphabetical StringsGNU C++17Accepted30 ms0 KB
#include <bits/stdc++.h>

using namespace std;

void solve() {
	char s[50];
	scanf("%s", s);
	int n = (int) strlen(s);
	int l = 0, r = n - 1;				//初始化为原字符串的左右端
	while (n--) {
		if (s[l] == 'a' + n)++l;		//左边符合,左端点右移
		else if (s[r] == 'a' + n)--r;	//右边符合,右端点左移
		else {							//都不符合,得出结论
			printf("NO\n");
			return;
		}
	}
	printf("YES\n");
}

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

C. Pair Programming

题意

甲乙两人一起写一段代码,一开始已经有 k k k行代码,甲有 n n n个操作 a 1 , a 2 , … , a n a_1,a_2,\dots,a_n a1,a2,,an,乙有 m m m个操作 b 1 , b 2 , … , b m b_1,b_2,\dots,b_m b1,b2,,bm,其中,操作若为0,则为新增一行代码,若不为0,则为修改操作的数值对应的行,此时要求修改的这一行存在。要求在不改变甲乙各自操作的相对顺序的情况下(即 a 1 a_1 a1一定在 a 2 a_2 a2之前, b 1 b_1 b1一定在 b 2 b_2 b2之前,以此类推),找到任何一种合理的方案,若不存在,输出 − 1 -1 1

思路

由于修改操作不影响代码行数,新增操作可以增加代码行数,因此贪心即可。每次检查甲的操作是否可行,若可行(也就是说甲的下一操作是0或者修改已经存在的行),则执行(如果是0的话要记得更新行数),不可行则判断乙的操作,与上述甲操作相同,若还是不可行,说明没有合适的方案。

时间复杂度

O ( n + m ) O(n+m) O(n+m)

AC代码
ProblemLangVerdictTimeMemory
C - Pair ProgrammingGNU C++17Accepted77 ms200 KB
#include <bits/stdc++.h>

using namespace std;

int a[1000], b[1000];

void solve() {
	int k, n, m;
	scanf("%d%d%d", &k, &n, &m);
	for (int i = 0; i < n; ++i) {
		scanf("%d", &a[i]);
	}
	for (int i = 0; i < m; ++i) {
		scanf("%d", &b[i]);
	}
	int i = 0, j = 0;						//双指针,初始都是0
	vector<int> v;							//保存结果序列
	while (i != n || j != m) {
		if (i < n && a[i] <= k) {			//尝试执行甲操作
			v.push_back(a[i]);
			if (!a[i])++k;					//操作是0,要增加更新k
			++i;
		} else if (j < m && b[j] <= k) {	//尝试执行乙操作
			v.push_back(b[j]);
			if (!b[j])++k;
			++j;
		} else {							//都不可行,得出结论
			printf("-1\n");
			return;
		}
	}
	for (auto &p:v)printf("%d ", p);		//输出结果
	printf("\n");
}

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

D. Co-growing Sequence

题意

定义序列 a 1 , a 2 , … , a n a_1,a_2,\dots,a_n a1,a2,,angrowing,满足条件:对于第1到n-1项,均满足 a i & a i + 1 = a i a_i\&a_{i+1}=a_i ai&ai+1=ai,即自身与下一项按位与后的结果等于自身。定义两个序列 a 1 , a 2 , … , a n a_1,a_2,\dots,a_n a1,a2,,an b 1 , b 2 , … , b n b_1,b_2,\dots,b_n b1,b2,,bnco-growing,满足条件:两序列各项按位异或后得到的序列是growing的。现给出序列 a 1 , a 2 , … , a n a_1,a_2,\dots,a_n a1,a2,,an,求字典序最小的序列 b 1 , b 2 , … , b n b_1,b_2,\dots,b_n b1,b2,,bn,使得序列 a 1 , a 2 , … , a n a_1,a_2,\dots,a_n a1,a2,,an b 1 , b 2 , … , b n b_1,b_2,\dots,b_n b1,b2,,bnco-growing

思路

对于每一项 a i a_i ai,与 a i + 1 a_{i+1} ai+1按位与运算后不改变值,等价于 a i a_i ai中的每一个二进制位,只要该位为1,则 a i + 1 a_{i+1} ai+1中对应的位为1。题目要求序列 b 1 , b 2 , … , b n b_1,b_2,\dots,b_n b1,b2,,bn字典序最小,由于异或运算中,假定运算为 a   x o r   b a\ xor\ b a xor b b b b中某位为1意味着运算结果中该位的值与 a a a中这一位的值不同,因此只需让异或得到的序列尽可能地接近原序列即可,最接近的值为按位或的结果,再根据异或操作的可逆性求出要求的序列。

时间复杂度

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

AC代码
ProblemLangVerdictTimeMemory
D - Co-growing SequenceGNU C++17Accepted93 ms2400 KB
#include <bits/stdc++.h>

using namespace std;

int a[200005];						//读入的原序列
int b[200005];						//题目要求的序列
int c[200005];						//最优的合理序列

void solve() {
	int n;
	scanf("%d", &n);
	for (int i = 0; i < n; ++i) {
		scanf("%d", &a[i]);
	}
	b[0] = 0;						//第一项一定是0
	c[0] = a[0];
	for (int i = 1; i < n; ++i) {
		c[i] = a[i] | c[i - 1];		//求出最优序列,用按位或
		b[i] = a[i] ^ c[i];			//求出题目要求的序列,用按位异或
	}
	for (int i = 0; i < n; ++i) {
		printf("%d ", b[i]);
	}
	printf("\n");
}

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

E. Air Conditioners

题意

长为 n n n的一维区间里有 k k k个空调,不存在重合的情况,每个空调能在其所属的位置造成温度为 t t t,每远离1单位距离,温度就上升1,一个点的温度是所有空调在该点造成温度的最小值。现给出区间长度和空调数量,以及每个空调的位置和设定温度,求每个点的温度。

思路

首先需要认识到一点:一个位于 x x x的空调,设定温度为 t t t,在向两侧传播的时候,相当于在 x − 1 x-1 x1位置和 x + 1 x+1 x+1位置各新增一个温度设定为 t + 1 t+1 t+1的空调。利用这一点,我们把空调按设定温度从小到大排序,然后依次处理,直到所有单元都被处理过。处理时,先加入温度为 t 0 t_0 t0的所有空调,然后尝试使之向两侧传播,传播时,将传播得到的新的点也看作一个空调即可,利用队列依次处理完所有温度为 t 0 t_0 t0的情形后,开始处理温度为 t 0 + 1 t_0+1 t0+1的情形,重复以上操作即可。注意:本题使用优先队列会超时,必须使用队列等效率更高的实现方法。

时间复杂度

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

AC代码
ProblemLangVerdictTimeMemory
E - Air ConditionersGNU C++17Accepted233 ms12500 KB
#include <bits/stdc++.h>

using namespace std;

int a[300005];

struct dt {
	int x{}, t{}, d{};						//x表示位置,t表示温度,d表示传播方向

	dt() = default;

	dt(int xx, int tt, int dd) {
		x = xx, t = tt, d = dd;
	}

	bool operator<(dt &o) const {			//重载小于号,用于排序
		return t < o.t;
	}
} d[300005];

void solve() {
	int n, k;
	scanf("%d%d", &n, &k);
	for (int i = 1; i <= n; ++i) {
		a[i] = 2e9;							//初始化为无穷大
	}
	queue<dt> q;							//运用队列提高效率
	for (int i = 0; i < k; ++i) {
		scanf("%d", &d[i].x);
	}
	for (int i = 0; i < k; ++i) {
		scanf("%d", &d[i].t);
	}
	sort(d, d + k);							//按温度排序
	int cur = d[0].t;						//当前正在处理的温度
	int res = 1;							//已经处理过的位置数
	int point = 1;							//已经加入的空调
	a[d[0].x] = d[0].t;						//先加入温度最低的空调
	if (d[0].x > 1)q.push(dt(d[0].x - 1, d[0].t + 1, -1));	//尝试向左传播
	if (d[0].x < n)q.push(dt(d[0].x + 1, d[0].t + 1, 1));	//尝试向右传播
	while (res < n) {
		while (point < k && d[point].t == cur) {			//将等于当前正在处理温度的所有空调都加进来
			if (d[point].t >= a[d[point].x]) {				//这个位置已经被更新过了,直接跳过
				++point;
				continue;
			}
			a[d[point].x] = d[point].t;
			++res;							//更新成功,要记得计数已经更新过的位置数
			if (d[point].x > 1)q.push(dt(d[point].x - 1, d[point].t + 1, -1));
			if (d[point].x < n)q.push(dt(d[point].x + 1, d[point].t + 1, 1));
			++point;
		}
		while (q.front().t == cur) {
			auto p = q.front();
			q.pop();
			if (p.t >= a[p.x])continue;		//不能降温,跳过
			a[p.x] = p.t;
			++res;
			int to = p.x + p.d;				//传播的目的地
			if (to >= 1 && to <= n && p.t + 1 < a[to])q.push(dt(to, p.t + 1, p.d));
		}
		++cur;
	}
	for (int i = 1; i <= n; ++i) {
		printf("%d ", a[i]);
	}
	printf("\n");
}

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

F. Array Stabilization (GCD version)

题意

给定一个正整数序列 a 0 , a 1 , … , a n − 1 a_0,a_1,\dots,a_{n-1} a0,a1,,an1,现定义操作:将序列中的每一项替换为该项与下一项的最大公因数,每一个替换是同时进行的,其中 a n − 1 a_{n-1} an1的下一项是 a 0 a_0 a0。给出序列 a 0 , a 1 , … , a n − 1 a_0,a_1,\dots,a_{n-1} a0,a1,,an1,求经过多少次后,序列中的所有数都相等。

思路

首先考虑平衡状态:序列的最终平衡状态是所有数都等于整个序列的最大公因数。因此不妨先找出整个序列的最大公因数,每一项都除以该数,问题转化为多少次操作后,序列全为1。显然,如果此时所有数都是1,则操作为0次。除了这种情况外,操作次数一定在 [ 1 , n − 1 ] [1,n-1] [1,n1]区间中。再次转化问题:如果一个长度为 l e n len len的区间的最大公因数不为1,那么至少需要 l e n len len次操作,才能使这段区间全部转化为1。令 f ( x ) = 0 / 1 f(x)=0/1 f(x)=0/1,表示序列中能否找到长度为 x x x的连续子序列( a n − 1 a_{n-1} an1 a 0 a_0 a0也认为是连续的),使得该段序列的最大公因数不为1。显然这是一个不严格的递增函数,因此可以使用二分解决。为了求出一个区间的最大公因数,需要写一个稀疏表来加速。

时间复杂度

O ( n ⋅ l o g 2   2 n ) O(n\cdot log_2^{\ 2}n) O(nlog2 2n)

AC代码
ProblemLangVerdictTimeMemory
F - Array Stabilization (GCD version)GNU C++17Accepted764 ms16400 KB
#include <bits/stdc++.h>

using namespace std;

int n;
int a[200005];
int st[200005][20];						//稀疏表(st表),记录区间gcd

int get(int x, int k) {					//函数用于求从x开始长为k的区间的gcd
	int p = 0;
	while (1 << (p + 1) <= k)++p;
	if (1 << p == k)return st[x][p];
	else return __gcd(st[x][p], get((x + (1 << p)) % n, k - (1 << p)));
}

void solve() {
	scanf("%d", &n);
	for (int i = 0; i < n; ++i) {
		scanf("%d", &a[i]);
	}
	int g = a[0];
	for (int i = 1; i < n; ++i) {
		g = __gcd(g, a[i]);				//C++版本比较低,不支持__gcd函数的请手写gcd函数
	}
	bool ok = true;
	for (int i = 0; i < n; ++i) {
		a[i] /= g;
		if (a[i] > 1)ok = false;
	}
	if (ok) {							//特判不需要操作的情况
		printf("0\n");
		return;
	}
	for (int i = 0; i < n; ++i) {		//以下两个循环,预处理st表
		st[i][0] = a[i];
	}
	for (int i = 1; 1 << i <= n; ++i) {
		for (int j = 0; j < n; ++j) {
			st[j][i] = __gcd(st[j][i - 1], st[(j + (1 << (i - 1))) % n][i - 1]);
		}
	}
	int l = 1, r = n - 1;				//二分左右界
	int ans;							//二分答案
	while (l <= r) {
		int mid = (l + r) >> 1;			//二分中点
		int res = 0;
		for (int i = 0; i < n; ++i) {
			if (get(i, mid) != 1) {
				res = 1;
				break;
			}
		}
		if (!res)r = mid - 1;
		else ans = mid, l = mid + 1;
	}
	printf("%d\n", ans);
}

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

G. How Many Paths?

题意

给出一个有向图,可能有自环,问从1号顶点到每个顶点的连通性。连通性有以下4种:

  • 0:不连通
  • 1:唯一连通路径
  • 2:不唯一且有限的连通路径
  • -1:无限种连通路径
思路

解题关键是找到环,只要经过了环,就意味着从这个点开始,路径有无数种。找环等价于找强连通分量,找到强连通分量后dfs即可。dfs时,遇到已经确定为无限路径的点,直接剪枝即可,唯一路径点有通向多条路径的点,同样可以剪枝,其余剪枝情况详见代码,这样剪枝可以保证,每一个点的状态最多经历从0到1到2到-1的更新过程,也就是最多搜索 3 n 3n 3n次即可完成。

时间复杂度

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

AC代码
ProblemLangVerdictTimeMemory
G - How Many Paths?GNU C++17Accepted701 ms51200 KB
#include <bits/stdc++.h>

using namespace std;

vector<int> g1[400005], g2[400005];		//g1是正向建图,g2是反向建图
bool vis[400005];						//记录点有没有访问过
stack<int> s;							//用于存储dfs序用于倒序遍历
bool f[400005];							//记录该点是否在环上
int ans[400005];						//用于记录该点和1号点的连通情况

void add(int x, int y) {				//建边函数
	g1[x].push_back(y);
	g2[y].push_back(x);
}

void dfs1(int p) {						//第一次搜索,在反图上搜索,并为后序遍历做准备,p是点的号码
	if (vis[p])return;
	vis[p] = true;
	for (int i:g2[p]) {
		dfs1(i);
	}
	s.push(p);
}

bool dfs2(int p, int x) {				//第二次搜索,在原图上搜索,返回该点是否在环上,p是点的号码,x是环首
	if (!vis[p])return false;
	vis[p] = false;
	bool res = false;
	for (int i:g1[p]) {					//后续能到达环首,或能到达在环上的点,则该点在环上
		if (i == x)res = true;
		else if (vis[i])res |= dfs2(i, x);
	}
	f[p] = res;
	return res;
}

void dfs3(int p, int x) {				//第三次搜索,在原图上搜索,处理每一个点的连通信息,p是点的号码,x是连通性
	if (f[p])x = -1;					//这个点在环上,那么一定有无数种路径
	ans[p] = x;
	for (int i:g1[p]) {					//只有4种情况是要继续搜索的,其余情况一律剪枝
		if (ans[i] == -1)continue;		//下一个点在环上,直接不考虑,无穷大不管怎么加还是无穷大
		if (ans[i] == 2 && x == -1)dfs3(i, -1);				//当前点路径数无穷大,那么下一个点一定也是无穷大
		if (ans[i] == 1 && (x == -1 || x == 2))dfs3(i, x);	//当前点多种路径,下一个点也是多种路径
		if (ans[i] == 1 && x == 1)dfs3(i, 2);				//当前点唯一路径,下一个点路径增加一条
		if (ans[i] == 0)dfs3(i, x);							//下一个点无法到达,直接更新为当前点的连通性
	}
}

void solve() {
	int n, m;
	scanf("%d%d", &n, &m);
	while (m--) {
		int x, y;
		scanf("%d%d", &x, &y);
		add(x, y);						//建图
	}
	memset(vis, 0, sizeof vis);
	for (int i = 1; i <= n; ++i) {
		dfs1(i);						//第一次搜索
	}
	memset(f, 0, sizeof f);
	while (!s.empty()) {
		dfs2(s.top(), s.top());			//第二次搜索
		s.pop();
	}
	memset(ans, 0, sizeof ans);
	dfs3(1, 1);							//第三次搜索,1号点默认为1条路径
	for (int i = 1; i <= n; ++i) {
		printf("%d ", ans[i]);
	}
	printf("\n");
	for (int i = 1; i <= n; ++i) {		//用完记得把图清空
		g1[i].clear();
		g2[i].clear();
	}
}

int main() {
//	freopen("in.txt", "r", stdin);
	int t;
	scanf("%d", &t);
	while (t--) {
		solve();
	}
	return 0;
}
  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值