Codeforces Round #514 (Div. 2)

本文解析了Codeforces Round #514 (Div.2)的五道题目,涵盖工作时间安排、图案绘制验证、序列转换、坐标平面点包围及树结构分割等问题,提供了详细的算法思路与代码实现。

【比赛链接】

【A】Cashier

  • 【题意】一个人一天工作 l l l 分钟,他有 n n n 段工作时间,给出开始时间和工作长度,保证工作时间是若干个按先后顺序排列的不相交的区间。在非工作时间里,这个人每 a a a 分钟能休息一次,求这个人最多休息几次。
  • 【题解】按题意模拟即可。注意不要漏掉最后剩下的时间。
  • 时间复杂度 O ( N ) O(N) O(N)
  • 【代码】
#include<bits/stdc++.h>
#define	INF	0x3f3f3f3f
#define	LL	long long
using namespace std;
int n;
LL l, a, ans;

template <typename T> void chkmin(T &x, T y){x = min(x, y);}
template <typename T> void chkmax(T &x, T y){x = max(x, y);}
template <typename T> void read(T &x){
	x = 0; int f = 1; char ch = getchar();
	while (!isdigit(ch)) {if (ch == '-') f = -1; ch = getchar();}
	while (isdigit(ch)) {x = x * 10 + ch - '0'; ch = getchar();}
	x *= f;
}

int main(){
	read(n), read(l), read(a);
	LL last = 0;
	for (int i = 1; i <= n; ++i){
		LL u, v; read(u), read(v);
		v = u + v;
		ans += (u - last) / a;
		last = v;
	}
	ans += (l - last) / a;
	printf("%I64d\n", ans);
	return 0;
}

【B】Forgery

  • 【题意】给出一个由 ‘#’ 和 ‘.’ 组成的矩形。你有一个大小为 3 ∗ 3 3*3 33 的中心为 ‘.’ ,周围为 ‘#’ 的钢笔。问你是否能用这支笔画出和给出的矩形一样的图案。
  • 【题解】把每个能涂上颜色的位置都涂上颜色,最后检查一下是否和给出的图形一样。
  • 时间复杂度 O ( N ∗ M ) O(N*M) O(NM)
  • 【代码】
#include<bits/stdc++.h>
#define	INF	0x3f3f3f3f
#define	LL	long long
#define	MAXN	1010
using namespace std;
const int dx[] = {-1, -1, -1, 0, 0, 1, 1, 1};
const int dy[] = {-1, 0, 1, -1, 1, -1, 0, 1};
int n, m;
char s[MAXN][MAXN], a[MAXN][MAXN];

template <typename T> void chkmin(T &x, T y){x = min(x, y);}
template <typename T> void chkmax(T &x, T y){x = max(x, y);}
template <typename T> void read(T &x){
	x = 0; int f = 1; char ch = getchar();
	while (!isdigit(ch)) {if (ch == '-') f = -1; ch = getchar();}
	while (isdigit(ch)) {x = x * 10 + ch - '0'; ch = getchar();}
	x *= f;
}

int main(){
	read(n), read(m);
	for (int i = 1; i <= n; ++i)
		scanf("%s", s[i] + 1);
	for (int i = 1; i <= n; ++i)
		for (int j = 1; j <= m; ++j)
			a[i][j] = '.';
	for (int i = 2; i < n; ++i)
		for (int j = 2; j < m; ++j){
			int fla = 1;
			for (int t = 0; t < 8; ++t){
				int tx = i + dx[t], ty = j + dy[t];
				if (s[tx][ty] != '#') {fla = 0; break;}
			}
			if (fla) 
				for (int t = 0; t < 8; ++t){
					int tx = i + dx[t], ty = j + dy[t];
					a[tx][ty] = '#';
				}
		}
	for (int i = 1; i <= n; ++i)
		for (int j = 1; j <= m; ++j)
			if (a[i][j] != s[i][j]) {printf("NO\n"); return 0;}
	printf("YES\n");
	return 0;
}

【C】Sequence Transformation

  • 【题意】现在有一个由 1 1 1 ~ n n n 构成的序列,你按照以下要求构造一个新的序列:在新序列后加入目前序列中所有数的gcd,然后在当前序列中删除一个数,重复该操作直到原序列中没有数。现在要求输出字典序最大的新序列。
  • 【题解】考虑字典序最大,那么就要尽量使得在前面的数变大,也就是要使得gcd变化时删除的数尽量少。显然,要删除最少的数使得一个由 1 1 1 ~ n n n 构成的序列的gcd变大,应该把所有奇数删去,构造出 ⌊ n + 1 2 ⌋ \lfloor \frac{n+1}{2} \rfloor 2n+1 个1。然后剩下的都是偶数,将每个数都除以2,问题范围就缩小成了 ⌊ n 2 ⌋ \lfloor \frac{n}{2} \rfloor 2n,且之后构造出的序列中每个数要乘2,递归处理即可。注意要特判 n = 3 n=3 n=3 的情况。
  • 时间复杂度 O ( N ) O(N) O(N)
  • 【代码】
#include<bits/stdc++.h>
#define	INF	0x3f3f3f3f
#define	LL	long long
using namespace std;
int n;

template <typename T> void chkmin(T &x, T y){x = min(x, y);}
template <typename T> void chkmax(T &x, T y){x = max(x, y);}
template <typename T> void read(T &x){
	x = 0; int f = 1; char ch = getchar();
	while (!isdigit(ch)) {if (ch == '-') f = -1; ch = getchar();}
	while (isdigit(ch)) {x = x * 10 + ch - '0'; ch = getchar();}
	x *= f;
}

void work(int n, int d){
	if (n == 1) {printf("%d ", d); return;}
	if (n == 2) {printf("%d %d ", d, 2 * d); return;}
	if (n == 3) {printf("%d %d %d ", d, d, d * 3); return;}
	for (int i = 1; i <= n / 2 + (n % 2); ++i)
		printf("%d ", d);
	work(n / 2, d * 2);
}

int main(){
	read(n);
	work(n, 1);
	printf("\n");
	return 0;
}

【D】Nature Reserve

  • 【题意】给出笛卡尔平面上的 n n n个点,保证纵坐标不为 0 0 0,问能否用一个与坐标轴横轴相切的圆,围住所有点,如果可以的话,求最小的半径。
  • 【题解】首先,无解的情况是既存在纵坐标为正数的点,也存在纵坐标为负数的点。不妨设现在所有点的纵坐标都为正数,否则我们可以把所有点的纵坐标取反,对答案没有影响。如果半径为 R R R 的圆满足条件,那么半径比 R R R 大的圆也满足条件,答案具有可二分性。由于圆与坐标轴横轴线切,所以圆心的纵坐标为 R R R,那么对于每一个点,要使其在圆内的圆心的横坐标的范围通过简单的数学计算就可以得到。判断可行区间是否存在交集就可以判断目前答案的可行性。注意精度问题。
  • 时间复杂度 O ( N l o g P R E C I S I O N ) O(NlogPRECISION) O(NlogPRECISION)
  • 【代码】
#include<bits/stdc++.h>
#define	INF	0x3f3f3f3f
#define	LL	long long
#define	LD	long double
#define	MAXN	100010
using namespace std;
const LD eps = 1e-10;
int n;
struct dot{LD x, y;}a[MAXN]; 

template <typename T> void chkmin(T &x, T y){x = min(x, y);}
template <typename T> void chkmax(T &x, T y){x = max(x, y);}
template <typename T> void read(T &x){
	x = 0; int f = 1; char ch = getchar();
	while (!isdigit(ch)) {if (ch == '-') f = -1; ch = getchar();}
	while (isdigit(ch)) {x = x * 10 + ch - '0'; ch = getchar();}
	x *= f;
}

dot get(dot a, LD R){
	dot ret;
	ret.x = 1e7, ret.y = -1e7;
	LD tmp = a.y - R;
	if (tmp < 0) tmp = -tmp;
	if (tmp > R + eps) return ret;
	LD tnp = R - tmp;
	if (tnp <= 0) tnp = 0;
	else tnp = sqrt(tnp);
	LD delta = tnp * sqrt(R + tmp);
	ret.x = a.x - delta, ret.y = a.x + delta;
	return ret;
}

bool ok(LD R){
	dot cur;
	cur.x = -1e7, cur.y = 1e7;
	for (int i = 1; i <= n; ++i){
		dot tmp = get(a[i], R);
		chkmax(cur.x, tmp.x);
		chkmin(cur.y, tmp.y);
		if (cur.x > cur.y + 1e-9) return 0;
	}
	return 1;
}

int main(){
	read(n);
	int z = 0, f = 0;
	for (int i = 1; i <= n; ++i){
		read(a[i].x), read(a[i].y);
		if (a[i].y < 0) {
			f = 1;
			a[i].y = -a[i].y;
		} else z = 1;
	}
	if (z && f) {printf("-1\n"); return 0;}
	LD l = 0, r = 1e15;
	while (l + eps < r){
		LD mid = (l + r) / 2;
		if (l == mid || r == mid) break;
		if (ok(mid)) r = mid;
		else l = mid;
	}
	cout << fixed;
	cout << setprecision(12) << l << endl;
	return 0;
}

【E】Split the Tree

  • 【题意】给出一棵以 1 1 1 号节点为根的有根树。定义一条垂直路径为一个点到它某个祖先(包括本身)的路径上的所有点。现在要将给出的树分为若干条垂直路径,使得每条垂直路径上的点数不大于 L L L,点权和不大于 S S S。若存在合法的划分,输出最少能划分成几条垂直路径,否则输出 − 1 -1 1
  • 【题解】首先,我们可以用树上倍增预处理出从每个点开始,最高可以将路径延伸到哪个点。从子树开始向上进行划分,有一个显然的贪心做法,在儿子中选择可以延伸得最高的延伸到这个节点上,其余儿子向下形成独立的垂直路径,统计入答案 。
  • 时间复杂度 O ( N l o g N ) O(NlogN) O(NlogN)
  • 【代码】
#include<bits/stdc++.h>
#define	INF	0x3f3f3f3f
#define	LL	long long
#define	MAXN	100010
#define	MAXLOG	20
using namespace std;
LL w[MAXN], S;
int n, L, sonsize[MAXN], frm[MAXN], dep[MAXN], fa[MAXN][MAXLOG + 2], top[MAXN], ans;
vector <int> a[MAXN];

template <typename T> void chkmin(T &x, T y){x = min(x, y);}
template <typename T> void chkmax(T &x, T y){x = max(x, y);}
template <typename T> void read(T &x){
	x = 0; int f = 1; char ch = getchar();
	while (!isdigit(ch)) {if (ch == '-') f = -1; ch = getchar();}
	while (isdigit(ch)) {x = x * 10 + ch - '0'; ch = getchar();}
	x *= f;
}

void init(int pos, int dad){
	dep[pos] = dep[dad] + 1, fa[pos][0] = dad, w[pos] += w[dad];
	for (int i = 1; (1 << i) <= dep[pos]; ++i)
		fa[pos][i] = fa[fa[pos][i - 1]][i - 1];
	int tmp = pos;
	for (int i = MAXLOG; i >= 0; --i)
		if (fa[tmp][i] && dep[pos] - dep[fa[tmp][i]] + 1 <= L && w[pos] - w[fa[fa[tmp][i]][0]] <= S)
			tmp = fa[tmp][i];
	top[pos] = tmp;
	for (unsigned i = 0, si = a[pos].size(); i < si; ++i){
		int son = a[pos][i];
		init(son, pos);
	}
}

void dfs(int pos, int dad){
	frm[pos] = pos;
	int tmp = pos, tnp = 0;
	for (unsigned i = 0, si = a[pos].size(); i < si; ++i){
		int son = a[pos][i];
		dfs(son, pos);
		if (dep[top[frm[son]]] <= dep[tmp]) tmp = top[frm[son]], tnp = frm[son];
	}
	if (tnp == 0) {
		frm[pos] = pos;
		ans += sonsize[pos];
	} else {
		frm[pos] = tnp;
		ans += sonsize[pos] - 1; 
	}
}

int main(){
	read(n), read(L), read(S);
	for (int i = 1; i <= n; ++i){
		read(w[i]);
		if (w[i] > S) {printf("-1\n"); return 0;}
	}
	for (int i = 2; i <= n; ++i){
		int t; read(t);
		a[t].push_back(i);
		++sonsize[t];
	}
	init(1, 0);
	dfs(1, 0);
	++ans;
	printf("%d\n", ans);
	return 0;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值