Educational Codeforces Round 112 (Rated for Div. 2)

Educational Codeforces Round 112 (Rated for Div. 2)

A - PizzaForces

\(3\)种规格的pizza,分别有\(6,8,10\)块,需要烤制的时间分别为\(15,20,25\)

若要至少获得\(n\)块,则最小烤制时间为多少。

对于\(100\%\)的数据\(1 \leq n\ \leq 10^{16}\)

题目要求\(6x+8x+10z \geq n\)的条件下,最小化\(15x+20y+25z\)的值。

注意到无论什么规格的pizza,烤制每一块的时间都是一定的,即\(\frac{15}{6}=\frac{20}{8}=\frac{25}{10}=2.5\)

注意到\(6x+8x+10z \geq n\)等价于\(3x+4x+5z \geq \lceil \frac{n}{2} \rceil = (n+1)/2\).

考虑到\(3,5\)互质,可以知道对于任意一个大于等于\(8\)的整数都可以用若干个\(3,5\)相加得到。

因此,当\(n\geq 15\)的时候,答案就是\((n+1)/2 *5\).当\(n \leq 14\)时,可以手算枚举。

进一步化简式子,发现当\(n\geq 6\)时候答案始终等于\((n+1)/2 *5\),当\(n\leq 6\)的时候,答案始终等于\(15\)

时间复杂度\(O(1)\)

# include <bits/stdc++.h>
# define int long long
using namespace std;

signed main() {
	int t;
	scanf("%lld\n", &t);
	while (t--) {
		int n, ans = 0;
		scanf("%lld", &n);
		if (n < 6)
			puts("15");
		else
			printf("%lld\n", (n + 1) / 2 * 5);
	}
	return 0;
}

B - Two Tables

\(W\times H\)房间里有以\((x_1,y1)\)为左下角,\((x_2,y_2)\)为右下角的矩形,现在又想放下一个\(w\times h\)的矩形。

求出以\((x_1,y1)\)为左下角,\((x_2,y_2)\)为右上角的矩形最小移动距离。

若无法实现,则输出\(-1\)

对于\(100\%\)的数据\(10^8\leq W,H \leq 10^8\)

左、右、上、下中选择一个方向平移是最优解。

左右平移可以让高不受\(h\)的限制,上下平移可以让宽不受\(w\)的限制。

因此,将四种情况分别讨论,就可以得到最小距离了。

可以证明,最小移动距离一定是整数。

时间复杂度\(O(1)\)

#include <cstdio>
#include <algorithm>
using namespace std;
 
int main() {
	int t;
	scanf("%d", &t);
	while (t--) {
		int W, H, w, h, x1, y1, x2, y2;
		scanf("%d%d", &W, &H);
		scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
		scanf("%d%d", &w, &h);
		int ans = 1e9;
		if (h + y2 - y1 > H && w + x2 - x1 > W)
			ans = -1;
		else {
			if (h + y2 - y1 <= H) {
				if (y2 > H - h)
					ans = min(ans, y2 - H + h);
				else
					ans = 0;
				if (y1 < h)
					ans = min(ans, h - y1);
				else
					ans = 0;
			}
			if (w + x2 - x1 <= W) {
				if (x1 < w)
					ans = min(ans, w - x1);
				else
					ans = 0;
				if (x2 > W - w)
					ans = min(ans, x2 - W + w);
				else
					ans = 0;
			}
 
		}
		if (ans == -1)
			puts("-1");
		else
			printf("%.7lf\n", (double) ans);
	}
 
	return 0;
}

C - Coin Rows

一个\(2\times m\)的格子中有金币,一条合法路线是从左到右平移一格或者从上到下平移一格。

\(A,B\)两人分别想从\((1,1)\)走到\((2,m)\),并把格子中的金币取走,\(A\)先走,\(B\)后走。

最终的得分是\(B\)获得的金币数,\(A\)想最小化得分,\(B\)想最大化得分。

求最优策略下游戏的最优得分是多少。

对于\(100\%\)的数据\(1 \leq m\leq 10^5 , 1 \leq a_{i,j} \leq 10^4\)

注意到格子是\(2\times m\),因此每个人能且只能在一个地方从上到下走。

枚举\(A\)从上到下走的位置,考虑\(B\)的最优路径。

  • \((1,1)\)处向下走,收集第\(2\)行的剩余金币。

  • \((1,m)\)处向下走,收集第\(1\)的剩余金币。

否则,和\(A\)路径重合部分会更多,必然不优。

由于\(B\)追求得分最大,因此他会在上述两个方案中选择最优的方案执行。

由于\(A\)追求得分最小,因此他会在所有可能向下走的位置,让\(B\)的最优决策最小化。

时间复杂度\(O(m)\)

# include <bits/stdc++.h>
# define int long long
using namespace std;
const int N = 1e5 + 10;
int a[N], b[N], sa[N], sb[N];
 
signed main() {
	int t;
	scanf("%lld", &t);
	while (t--) {
		int n;
		scanf("%lld", &n);
		for (int i = 1; i <= n; i++)
			scanf("%lld", &a[i]), sa[i] = sa[i - 1] + a[i];
		for (int i = 1; i <= n; i++)
			scanf("%lld", &b[i]), sb[i] = sb[i - 1] + b[i];
		int ans = sa[n] + sb[n];
		for (int i = 1; i <= n; i++) {
			int res = max(sb[i - 1], sa[n] - sa[i]);
			ans = min(ans, res);
		}
		printf("%lld\n", ans);
	}
	return 0;
}

D - Say No to Palindromes

给出一个由\(a,b,c\)组成的字符串\(s\),有\(q\)个询问。

每个询问给出\(l_i,r_i\),考虑子串\(t = s[l_i]s[l_i+1]...s[r_i-1]s[r_i]\)

输出最小更改子串中字母的个数,使得子串中没有长度大于等于\(2\)的回文子串。

对于\(100\%\)的数据\(1 \leq n,q \leq 2\times 10^5\)

考虑到只含三个字母字符串,能构成没有长度大于等于\(2\)的回文子串的情况只有:

abcabcabc...

acbacbacb...

bacbacbac...

bcabcabca...

cabcabcab...

cbacbacba...

这六种情况是轮换对称的,因此只需要一个简单的前缀和就可以解决本问题。

时间复杂度\(O(n+q)\)

# include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
int a[7][N], n, q;
char s[N];
 
int main() {
	scanf("%d%d", &n, &q);
	scanf("%s", s);
	int cnt = 0;
	for (int i = 0; i < n; i++) {
		if (i % 3 == 0 && s[i] != 'a')
			cnt++;
		if (i % 3 == 1 && s[i] != 'b')
			cnt++;
		if (i % 3 == 2 && s[i] != 'c')
			cnt++;
		a[1][i] = cnt;
	}
	cnt = 0;
	for (int i = 0; i < n; i++) {
		if (i % 3 == 0 && s[i] != 'a')
			cnt++;
		if (i % 3 == 1 && s[i] != 'c')
			cnt++;
		if (i % 3 == 2 && s[i] != 'b')
			cnt++;
		a[2][i] = cnt;
	}
	cnt = 0;
	for (int i = 0; i < n; i++) {
		if (i % 3 == 0 && s[i] != 'b')
			cnt++;
		if (i % 3 == 1 && s[i] != 'a')
			cnt++;
		if (i % 3 == 2 && s[i] != 'c')
			cnt++;
		a[3][i] = cnt;
	}
	cnt = 0;
	for (int i = 0; i < n; i++) {
		if (i % 3 == 0 && s[i] != 'b')
			cnt++;
		if (i % 3 == 1 && s[i] != 'c')
			cnt++;
		if (i % 3 == 2 && s[i] != 'a')
			cnt++;
		a[4][i] = cnt;
	}
	cnt = 0;
	for (int i = 0; i < n; i++) {
		if (i % 3 == 0 && s[i] != 'c')
			cnt++;
		if (i % 3 == 1 && s[i] != 'a')
			cnt++;
		if (i % 3 == 2 && s[i] != 'b')
			cnt++;
		a[5][i] = cnt;
	}
	cnt = 0;
	for (int i = 0; i < n; i++) {
		if (i % 3 == 0 && s[i] != 'c')
			cnt++;
		if (i % 3 == 1 && s[i] != 'b')
			cnt++;
		if (i % 3 == 2 && s[i] != 'a')
			cnt++;
		a[6][i] = cnt;
	}
	while (q--) {
		int l, r;
		scanf("%d%d", &l, &r);
		l--;
		r--;
		int ans = n;
		for (int i = 1; i <= 6; i++)
			ans = min(ans, a[i][r] - a[i][l - 1]);
		printf("%d\n", ans);
	}
	return 0;
}

E - Boring Segments

给出\(n\)个有权线段\((l_i,r_i,w_i)\)

一个合法的子集是使用这些线段可以铺满\([1,m]\),可以有重复路段,但是必须收尾相接。

合法子集得分是子集中线段最大权和最小权的差值。

求所有合法子集得分最小值,题目保证至少存在一个合法子集。

对于\(100\%\)的数据\(1 \leq n\leq 3\times 10^5, 2\leq m \leq 10^6\)

首先按照线段权值从小到大排序,显然在差值一定的情况下,将所有符合差值的线段都加入子集优于部分符合差值的线段都加入子集的情况。

考虑双指针,每一次都保证双指针中间的线段构成的子集恰好能铺满\([1,m]\),最小差值就是最右侧线段权值减去最左侧线段权值。

为了判断是否恰好能铺满\([1,m]\),我们要求一个支持以下操作的数据结构:

  • 区间加
  • 查询\([1,m]\)是否全部大于\(0\) (这等价于区间最小值)

因此,由于区间加和求区间最小值具有可加性,所以可以用线段树实现。

为了保证线段收尾相接,我们将\([1,m]\)中两个点中插入一个点,扩展为\([1,2*m-1]\)的区间即可。

时间复杂度\(O(n log_2 m)\)

# include <bits/stdc++.h>
# define ls (2*x)
# define rs (2*x+1)
using namespace std;
const int N = 3e5 + 10;
const int M = 8e6 + 10;
int n, m;
 
inline int read() {
	int x = 0;
	char c = getchar();
	while (c < '0' || c > '9')
		c = getchar();
	while (c >= '0' && c <= '9')
		x = x * 10 + c - '0', c = getchar();
	return x;
}
 
struct rec {
	int l, r, w;
} a[N];
 
bool cmp(rec a, rec b) {
	return a.w < b.w;
}
 
struct tree {
	int tag, val;
} tr[M];
 
void down(int x, int l, int r) {
	int mid = (l + r) / 2;
	tr[ls].tag += tr[x].tag;
	tr[ls].val += tr[x].tag;
	tr[rs].tag += tr[x].tag;
	tr[rs].val += tr[x].tag;
	tr[x].tag = 0;
}
 
void build(int x, int l, int r) {
	tr[x].tag = 0;
	tr[x].val = 0;
	if (l == r)
		return;
	int mid = (l + r) / 2;
	build(ls, l, mid);
	build(rs, mid + 1, r);
}
 
void update(int x, int l, int r, int opl, int opr, int val) {
	if (opl <= l && r <= opr) {
		tr[x].tag += val;
		tr[x].val += val;
		return;
	}
	down(x, l, r);
	int mid = (l + r) / 2;
	if (opl <= mid)
		update(ls, l, mid, opl, opr, val);
	if (opr >= mid + 1)
		update(rs, mid + 1, r, opl, opr, val);
	tr[x].val = min(tr[ls].val, tr[rs].val);
}
 
bool check(int key) {
	build(1, 1, 2 * m - 1);
	int i = 1, j = 1;
	while (i <= n && j <= n) {
		while (a[j].w - a[i].w <= key && j <= n) {
			update(1, 1, 2 * m - 1, a[j].l * 2 - 1, a[j].r * 2 - 1, 1);
			j++;
		}
		if (tr[1].val > 0)
			return 1;
		update(1, 1, 2 * m - 1, 2 * a[i].l - 1, 2 * a[i].r - 1, -1);
		i++;
	}
	return 0;
}
 
int main() {
	n = read(), m = read();
	for (int i = 1; i <= n; i++) {
		a[i].l = read();
		a[i].r = read();
		a[i].w = read();
	}
	sort(a + 1, a + 1 + n, cmp);
	int ans = a[n].w - a[1].w;
	int l = 1;
	for (int r = 1; r <= n; r++) {
		update(1, 1, 2 * m - 1, 2 * a[r].l - 1, 2 * a[r].r - 1, 1);
		for (; tr[1].val;) {
			ans = min(ans, a[r].w - a[l].w);
			update(1, 1, 2 * m - 1, 2 * a[l].l - 1, 2 * a[l].r - 1, -1);
			++l;
		}
	}
	printf("%d\n", ans);
	return 0;
}
posted @ 2021-07-31 12:35  Maystern  阅读(51)  评论(0编辑  收藏  举报