Educational Codeforces Round 112

时间:2021-07-31
本文章向大家介绍Educational Codeforces Round 112,主要包括Educational Codeforces Round 112使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

CF1555

场上没做出来 EF 的蒟蒻瑟瑟发抖。

\(A\) 直接 WA 了 \(4\) 发,然后 \(B,C,D\) 加起来不超过半个小时(不愧是我

A

需要制作 \(n\)​ 块饼,每次必须制作 \(6/8/10\)​ 个饼,每块饼需要 \(2.5\)​ 个单位时间。

求制作至少 \(n\)​ 块饼的最短时间。

模拟题。

显然是需要最小化多出的饼的数量,首先对于 \(<6\)​ 的情况直接特判掉。

\(n\) 为偶数,则一定有完美解,因为 \(8\ {\rm mod}\ 6=2,10\ {\rm mod}\ 6=4\)

而偶数 \(\rm mod\ 6\)​​ 一定为偶数,所以一定可以通过大量的 \(6\)​​ 和一个 \(6/8/10\)​​ 来补全。

\(n\) 为奇数,那么 \(n+1\) 为偶数一定有完美解,所以只需要 \(n+1\) 的时间。

代码不放。

B

\(W\times H\) 的大矩形中,原本存在一个给定的小矩形,要求放入另一个 \(w\times h\) 的矩形。

要求两个矩形不能重叠(边可以重叠)摆放。

求使 \(w\times h\)​​​ 矩形能够合法放入的条件下,给定矩形需要移动的最小距离,无解输出 -1

简单贪心。

先判断无解的情况,设给定矩形的长宽分别为 \(w',h'\)

不难发现若 \(w>W\)\(h>H\)\(w+w'>W\ \&\ h+h'>H\) 则无解。

在必定有解的情况下,显然只需要上下/左右移动就能够满足要求,简单模拟一下移动的最终方位即可。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

typedef double DB;
const DB INF = 1e15;

int T, W, H, w, h, x1, x2, y1, y2;

int read(){
	int x = 0, f = 1; char c = getchar();
	while(c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
	while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
	return x * f;
}

int main(){
	T = read();
	while(T --){
		W = read(), H = read();
		x1 = read(), y1= read(), x2 = read(), y2 = read();
		w = read(), h = read();
		if(w > W || h > H || (x2 - x1 + w > W && y2 - y1 + h > H)){puts("-1"); continue;}

		DB ans = INF;
		if(x2 - x1 + w <= W){
			if(x1 >= w) ans = 0.0;
			else ans = min(ans, (DB)w - x1);
			if(x2 <= W - w) ans = 0.0;
			else ans = min(ans, (DB)x2 - W + w);
		}
		if(y2 - y1 + h <= H){
			if(y1 >= h) ans = 0.0;
			else ans = min(ans, (DB)h - y1);
			if(y2 <= H - h)	ans = 0.0;
			else ans = min(ans, (DB)y2 - H + h);
		}
		printf("%.9lf\n", ans);
	}
	return 0;
}

C

在一个 \(2\times n\) 的网格图中,每个格子上有价值,\(A,B\) 两者先后从 \((1,1)\) 出发,去到 \((2,n)\)

每次移动只能向右/下移动(显然不能出格),第一次到达一个格子时将获得它的价值,然后该格价值清零。

\(A\) 希望 \(B\) 得到的价值最小,而 \(B\) 显然希望自己得到的价值最大,求两人都使用最优决策时,\(B\) 得到的值。

模拟题。

枚举每种 \(A\) 走的路径,显然是一种比较抽象的 "Z" 型路径,总共就 \(n\) 种。

然后 \(B\)​ 只能 第一行的右边部分/第二行的左边部分 二选一,这里取个 \(\max\),总体取个 \(\min\) 即可。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int N = 100010;
const int INF = 2e9;
int T, n, a[N], b[N];

int read(){
	int x = 0, f = 1; char c = getchar();
	while(c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
	while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
	return x * f;
}

int main(){
	T = read();
	while(T --){
		n = read();
		for(int i = 0; i <= n + 1; i ++) a[i] = b[i] = 0;
		for(int i = 1; i <= n; i ++) a[i] = read();
		for(int i = 1; i <= n; i ++) b[i] = read();
		for(int i = 1; i <= n; i ++) b[i] += b[i - 1];
		for(int i = n; i >= 1; i --) a[i] += a[i + 1];
		int ans = INF;
		for(int i = 1; i <= n; i ++)
			ans = min(ans, max(b[i - 1], a[i + 1]));
		printf("%d\n", ans);
	}
	return 0;
}

D

给定一个字符串,对此询问它的某个子串。

求将它变成一个没有长度 \(\geq 2\) 的回文子串的最少变换次数。

模拟题(但是被我想复杂了很多)

不难发现,问题一定有解,因为条件比较苛刻,所以最终串一定长这样:abcabc...

当然,开头可以是 abc\(6\) 个全排列中的一个,开头确定了也就确定了。

问题就变为求 \(6\) 种字符串与当前串的不同位数,暴力统计显然是 \(O(nm)\) 的。

然后我就直接上倍增优化了一下,变为 \(O(m\log n)\) 了,自带 \(6\) 倍常数,荣获最劣解。

#include<cstdio>
#include<string>
#include<cstring>
#include<algorithm>
using namespace std;

const int N = 2e5 + 10;
int n, m, f[6][N][18];
char str[N];
string cmp[6] = {"abc", "acb", "bac", "bca", "cab", "cba"};

int read(){
	int x = 0, f = 1; char c = getchar();
	while(c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
	while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
	return x * f;
}

int Get(int t, int p, int k){
	int sum = 0;
	for(int i = 17; i >= 0; i --)
		if(k >> i & 1){
			sum += f[t][p][i];
			p += 3 * (1 << i);
		}
	return sum;
}

int main(){
	n = read(), m = read();
	scanf("%s", str + 1);
	for(int t = 0; t < 6; t ++)
		for(int i = 1; i <= n - 2; i ++)
			for(int j = 0; j < 3; j ++)
				f[t][i][0] += (str[i + j] != cmp[t][j]);
	for(int t = 0; t < 6; t ++)
		for(int j = 1; j <= 17; j ++)
			for(int i = 1; i + (1 << j) * 3 - 1 <= n; i ++)
				f[t][i][j] = f[t][i][j - 1] + f[t][i + 3 * (1 << (j - 1))][j - 1];
	while(m --){
		int l = read(), r = read();
		int L = r - l + 1;
		int ans = N;
		for(int t = 0; t < 6; t ++){
			int now = 0;
			if(L >= 3) now += Get(t, l, L / 3);
			int R = r - (L % 3) + 1;
			for(int i = R; i <= r; i ++)
				now += (str[i] != cmp[t][i - R]);
			ans = min(ans, now);
		}
		printf("%d\n", ans);
	}
	return 0;
}

正解提前统计一下六种情况的前缀和,最后到当前子序列也一定对应不同的六种排列,只是同编号不太一样而已。

然后直接无脑 \(O(m)\) 就解决了...。代码略。

E

给定 \(n\) 个区间,每个有价值,求选择一些区间使得 \(1\sim n\) 都被覆盖且区间最大价值 - 最小价值的最小值。

长得像个二分答案,但是想了很久没思路,倒是想到了利用线段树模拟覆盖情况。

这题的思路几乎一样的,对于价值排序后,双指针得到合法区间,利用线段树模拟一下联通情况即可。

具体一点,对于区间按价值排序后,右指针每次移动至 \(1\sim n\) 全部覆盖,然后左指针同样移动至临界点。

这样的值显然是最小的,线段树维护一个 区间加 & 区间最小值 即可。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int N = 3e5 + 10;
const int M = 2e6 + 10;
const int INF = 2e9;

int n, m, dat[M << 1], add[M << 1];
struct Edge{int l, r, w;} a[N];

int read(){
	int x = 0, f = 1; char c = getchar();
	while(c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
	while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
	return x * f;
}

bool cmp(Edge a, Edge b){return a.w < b.w;}

int Min(int a, int b){return a < b ? a : b;}

void Push_Down(int p){
	if(!add[p]) return;
	int l = p << 1, r = p << 1 | 1;
	dat[l] += add[p], add[l] += add[p];
	dat[r] += add[p], add[r] += add[p];
	add[p] = 0;
}

void Modify(int p, int l, int r, int L, int R, int v){
	if(L <= l && r <= R) {dat[p] += v, add[p] += v; return;}
	Push_Down(p);
	int mid = (l + r) >> 1;
	if(L <= mid) Modify(p << 1, l, mid, L, R, v);
	if(R >  mid) Modify(p << 1 | 1, mid + 1, r, L, R, v);
	dat[p] = Min(dat[p << 1], dat[p << 1 | 1]);
}

int main(){
	n = read(), m = read() - 1;
	for(int i = 1; i <= n; i ++)
		a[i].l = read(), a[i].r = read() - 1, a[i].w = read();
	sort(a + 1, a + n + 1, cmp);
	
	int L = 0, R = 0, ans = INF;
	while(true){
		while(R + 1 <= n && dat[1] == 0)
			R ++, Modify(1, 1, m, a[R].l, a[R].r, 1);
		if(dat[1] == 0) break;
		while(L + 1 <= R && dat[1] >  0)
			L ++, Modify(1, 1, m, a[L].l, a[L].r, - 1);
		ans = Min(ans, a[R].w - a[L].w);
	}
	printf("%d\n", ans);
	return 0;
}

F

给定 \(n\) 个点,一开始没有连边,然后给定 \(m\) 个询问尝试不断加边,边权只能是 \(0/1\)

若加边之后,图中所有简单环(没有重复顶点的环)的异或和为 \(1\)​ 即可加入,否则不能加入。

对于每个询问输出 YES/NO 表示能否加边。

比较有思维难度。

一种加边的套路是首先得到一颗生成树,这真是一种好思路。

利用类似 Kruscal 的方法求出生成树,那么首先树上的边是一定可行的,因为这条边加入时不与任何边成简单环。

然后考虑非树边,显然只要这条非树边构成的环不与任何其它环相交即可。

若相交则一定出现 \(0\) 环,这个环是两个相交的环分别去掉相交部分,然后重新构成的环。

判断方法很多,可以考虑转化成 树上区间加 & 区间最大值,直接树剖维护即可,复杂度 \(O(m\log^2 n)\)

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int N = 3e5 + 10, M = 5e5 + 10;
int n, m, cnt, st[N], val[N], head[N];
bool vis[M];
struct edge{int u, v, w;} G[M];
struct Edge{int nxt, to, val;} ed[N << 1];

int read(){
	int x = 0, f = 1; char c = getchar();
	while(c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
	while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
	return x * f;
}

int Get(int x){
	if(x == st[x]) return x;
	return st[x] = Get(st[x]);
}

void add(int u, int v, int w){
	ed[++ cnt] = (Edge){head[u], v, w}; head[u] = cnt;
	ed[++ cnt] = (Edge){head[v], u, w}; head[v] = cnt; 
}

int tot, fa[N], id[N], son[N], sz[N], top[N], dep[N];

void dfs1(int u, int Fa){
	fa[u] = Fa, dep[u] = dep[Fa] + 1, sz[u] = 1;
	for(int i = head[u], v; i; i = ed[i].nxt)
		if((v = ed[i].to) != Fa){
			val[v] = val[u] ^ ed[i].val;
			dfs1(v, u);
			sz[u] += sz[v];
			if(!son[u] || sz[v] > sz[son[u]]) son[u] = v;
		}
}

void dfs2(int u, int Top){
	top[u] = Top, id[u] = ++ tot;
	if(!son[u]) return;
	dfs2(son[u], Top);
	for(int i = head[u], v; i; i = ed[i].nxt)
		if((v = ed[i].to) != fa[u] && v != son[u]) dfs2(v, v);
}

int dat[N << 2], cov[N << 2];

void Push_Down(int p){
	if(!cov[p]) return;
	int l = p << 1, r = p << 1 | 1;
	dat[l] = cov[l] = cov[p];
	dat[r] = cov[r] = cov[p];
	cov[p] = 0;
}

int Query(int p, int l, int r, int L, int R){
	if(L <= l && r <= R) return dat[p];
	Push_Down(p);
	int mid = (l + r) >> 1;
	if(L <= mid && Query(p << 1, l, mid, L, R)) return 1;
	if(R  > mid && Query(p << 1 | 1, mid + 1, r, L, R)) return 1;
	return 0; 
}

void Update(int p, int l, int r, int L, int R, int v){
	if(L <= l && r <= R) {dat[p] = cov[p] = v; return;}
	Push_Down(p);
	int mid = (l + r) >> 1;
	if(L <= mid) Update(p << 1, l, mid, L, R, v);
	if(R >  mid) Update(p << 1 | 1, mid + 1, r, L, R, v);
	dat[p] = max(dat[p << 1], dat[p << 1 | 1]);
}

bool Ask(int x, int y){
	while(top[x] != top[y]){
		if(dep[top[x]] < dep[top[y]]) swap(x, y);
		if(Query(1, 1, n + 1, id[top[x]], id[x])) return true;
		x = fa[top[x]];
	}
	if(dep[x] > dep[y]) swap(x, y);
	if(id[x] + 1 <= id[y] && Query(1, 1, n + 1, id[x] + 1, id[y])) return true;
	return false;
}

void Modify(int x, int y){
	while(top[x] != top[y]){
		if(dep[top[x]] < dep[top[y]]) swap(x, y);
		Update(1, 1, n + 1, id[top[x]], id[x], 1);
		x = fa[top[x]];
	}
	if(dep[x] > dep[y]) swap(x, y);
	if(id[x] + 1 <= id[y]) Update(1, 1, n + 1, id[x] + 1, id[y], 1);
}

int main(){
	n = read(), m = read();
	for(int i = 1; i <= n; i ++) st[i] = i;
	for(int i = 1; i <= m; i ++){
		int u = read(), v = read(), w = read();
		G[i] = (edge){u, v, w};
		int fu = Get(u), fv = Get(v);
		if(fu != fv)
			vis[i] = true, add(u, v, w), st[fu] = fv;
	}
	for(int i = 1; i <= n; i ++)
		if(!dep[i]) dfs1(i, 0), dfs2(i, i);
	
	for(int i = 1; i <= m; i ++){
		if(vis[i]) {puts("YES"); continue;}
		int u = G[i].u, v = G[i].v;
		int w = val[u] ^ val[v] ^ G[i].w;
		if(!w) {puts("NO"); continue;}

		if(Ask(u, v))
			puts("NO");
		else
			Modify(u, v), puts("YES");
	}
	return 0;
}

原文地址:https://www.cnblogs.com/lpf-666/p/15084909.html