VP Educational Codeforces Round 73 (Rated for Div. 2)


A. 2048 Game

题意:\(n\)个数都是\(2\)的幂,求能不能取一部分使得和为\(2^{11}\)

从小到大凑数。

点击查看代码
void solve() {
    int n;
    std::cin >> n;
    std::vector<int> cnt(30);
    for (int i = 0; i < n; ++ i) {
    	int x;
    	std::cin >> x;
    	++ cnt[std::__lg(x)];
    }
    for (int i = 0; i <= 10; ++ i) {
    	cnt[i + 1] += cnt[i] / 2;
    }
    if (cnt[11]) {
    	std::cout << "YES\n";
    } else {
    	std::cout << "NO\n";
    }
}

B. Knights

题意:一个\(n\times n\)棋盘,你要放满棋子,每个棋子是马的走法。有黑白两种棋子,不同棋子和互相攻击,你要是的互相攻击的棋子最多。

不管马的走法,还是上下左右,我们对棋盘进行黑白染色,使得没有相邻的格子颜色相同,那么走一步后一定到了不同的颜色。这样每个格子都和它可以走到的位置攻击。

点击查看代码
void solve() {
    int n;
    std::cin >> n;
    for (int i = 0; i < n; ++ i) {
    	for (int j = 0; j < n; ++ j) {
    		char c = (i + j) % 2 ? 'W' : 'B';
    		std::cout << c;
    	}
    	std::cout << "\n";
    }
}

C. Perfect Team

题意:有三类人,分别有\(c, m, x\)个。一队三个人,要求至少有一个一类和一个二类。剩下一个随便,求可以凑出来的最大队数。

显然有两个上界:\(\min(c, m), \lfloor \frac{c+m+x}{3} \rfloor\),如果\(\max(c - m) - \min(c, m) + x >= \min(c, m)\),则可以以满足第一个上界,否则满足第二个上界。直接取最小值就行。

点击查看代码
void solve() {
    int c, m, x;
    std::cin >> c >> m >> x;

    std::cout << std::min(std::min(c, m), (c + m + x) / 3) << "\n";
}

D. Make The Fence Great Again

题意:\(n\)个数,你要使得相邻两个数都不相同,每次可以对\(i\)花费\(b_i\)的操作使得\(a_i\)加一。求最小花费。

每个位置最多操作两次。一个位置最多需要操作两次使得和左右不同,如果此时右边需要加一导致和它相同,那么右边可以再加一,这样也是两次,也可以这个位置不操作,让前一个位置加一。总之不管怎么搞,一个位置操作两次以上是没有意义的。

那么可以\(dp\),记\(f[i][0/1/2]\)表示第\(i\)个位置操作了几次。

点击查看代码
void solve() {
    int n;
    std::cin >> n;
    std::vector<int> a(n), b(n);
    for (int i = 0; i < n; ++ i) {
    	std::cin >> a[i] >> b[i];
    }

    const i64 inf = 1e18;
    std::array<i64, 3> f{};
    f[0] = 0; f[1] = b[0]; f[2] = b[0] * 2;
    for (int i = 1; i < n; ++ i) {
    	std::array<i64, 3> g{inf, inf, inf};
    	for (int x = 0; x <= 2; ++ x) {
    		for (int y = 0; y <= 2; ++ y) {
    			if (a[i - 1] + x != a[i] + y) {
    				g[y] = std::min(g[y], f[x] + y * b[i]);
    			}
    		}
    	}
    	f = g;
    }
    std::cout << std::min({f[0], f[1], f[2]}) << "\n";
}

E. Game With String

题意:\(Alice\)\(Bob\)操作一个只包含'X'和'.'的字符串。每次\(Alice\)可以选择一段长度为\(a\)全是'.'的子串把它们都变成'X',而\(Bob\)是可以选择长度为\(b\)的子串。\(a > b\)。谁不能操作谁输,问\(Alice\)能不能赢。

首先把'.'通过'X'分成了很多段,这些段不能相互影响。那么我们讨论这些段的长度:
第一类\(len \geq b < a\),这个段只能让\(Bob\)操作,那么只要有这个段\(Bob\)就赢,因为如果\(Bob\)发现自己要输了,可以操作这个段逆转先手关系。
第二类\(a \leq len < 2b\),这个段\(Alice\)\(Bob\)可以有一个人操作一次,那么如果只有这一类,则有奇数个\(Alice\)赢,否则\(Bob\)赢。
第三类\(len \geq 2b\),对于这一类,\(Alice\)必须先手拆分它,不然\(Bob\)可以制造一个\(len \geq b < a\)的段,使得\(Bob\)必赢。那么这样的段只能由一个,否则\(Alice\)不可能阻止\(Bob\)
那么只需要讨论\(Alice\)在只有一个第三类的情况下能不能赢\(Bob\)
可以枚举\(Alice\)操作的位置,那么会将这个段分成两部分,这两部分不能有第一类,同时也不能分出来一个第三类,否则\(Bob\)马上制造一个第一类段。那么只会分出来第二类段,加上已有的二类段数量,看能不能赢。

点击查看代码
void solve() {
    int a, b;
    std::cin >> a >> b;
    std::string s;
    std::cin >> s;
    s = "X" + s + "X";
    int n = s.size();
    std::vector<int> c;
    for (int i = 0; i < n; ++ i) {
    	int j = i;
    	while (j + 1 < n && s[j + 1] != 'X') {
    		++ j;
    	}

    	if (j - i >= b) {
    		c.push_back(j - i);
    	}
    	i = j;
    }

    int cnt1 = 0, cnt2 = 0;
    for (auto & x : c) {
    	if (x < a) {
    		std::cout << "NO\n";
    		return;
    	}
    	if (x < 2 * b) {
    		++ cnt1;
    	} else if (x >= b * 2) {
    		++ cnt2;
    	}
    }

    if (cnt2 == 0) {
    	if (cnt1 & 1) {
    		std::cout << "YES\n";
    	} else {
    		std::cout << "NO\n";
    	}
    } else if (cnt2 > 1) {
    	std::cout << "NO\n";
    } else {
    	int len = *std::max_element(c.begin(), c.end());
    	for (int i = 1; i + a - 1 <= len; ++ i) {
    		int l = i - 1, r = len - (i + a - 1);
    		if (l >= 2 * b || r >= 2 * b || (l >= b && l < a) || (r >= b && r < a)) {
    			continue;
    		}

    		if ((cnt1 + (l >= a && l < 2 * b) + (r >= a && r < 2 * b)) % 2 == 0) {
    			std::cout << "YES\n";
    			return;
    		}
    	}
    	std::cout << "NO\n";
    }
}

F. Choose a Square

题意:平面上\(n\)个点,每个有\(c_i\)的价值。你要选择一个正方形,左下角和右上角的端点都在\(y=x\)这条直线上。这个正方形的价值为它包含的点的价值和减去正方形的边长。求最大价值。

可以把点看作一个线段,记正方形两个端点为\((l, l), (r, r)\),那么如果\(l \leq \min(x_i, y_i)\)\(\max(x_i, y_i) \leq r\)则包含在里面。因为是以\(y=x\)为对角线的正方形,那么\((x, y)\)\((y, x)\)是等价的,我们可以让\(x_i = \min(x_i, y_i), y_i = \max(x_i, y_i)\),也就是沿\(y=x\)翻转一下。那么就有\(x<y\),可以把他看作是\([x_i, y_i]\)的一条线段,那么一个正方形就是\([l, r]\)的一条线段,问题变为求\([l, r]\)包含的所有线段的价值和减去(r - l)的最大值。
我们先对点离散化。正方形两个段肯定是和某些点的坐标重合,不然不优。那么可以从大到小枚举\(x\)作为左端点,然后把\([y_i, m]\)都加上\(c_i\)\(m\)是下标最大的数。此时就变成了求最大的\([x_i, y]\)的最大和减去\(y - x_i\),发现\(y\)\(x_i\)无关,记和为\(f(x, y)\),可以写为\(f(x, y) - (y - x) = f(x, y) - y + x\)。因为\(x\)已经固定了,所以\(f(x, y)\)\(y\)都是可以求出来的,用区间修改加区间最大值的线段树就可以维护。

点击查看代码
#define ls (u << 1)
#define rs (u << 1 | 1)
#define umid (tr[u].l + tr[u].r >> 1)

template <class Info, class Tag>
struct Node {
	int l, r;
	Info info;
	Tag tag;
};

template <class Info, class Tag>
struct LazySegmentTree {
	std::vector<Node<Info, Tag> > tr;
	LazySegmentTree(int _n) {
		init(_n);
	}

	LazySegmentTree(std::vector<Info> & a) {
		int _n = (int)a.size();
		init(_n, a);
	}

	void init(int _n) {
		tr.assign(_n << 2, {});
		build(0, _n - 1);
	}

	void init(int _n, std::vector<Info> & a) {
		tr.assign(_n << 2, {});
		build(0, _n - 1, a);
	}

	void pushup(int u) {
		tr[u].info = tr[ls].info + tr[rs].info;
	}

	void pushdown(Node<Info, Tag> & u, Tag tag) {
		u.info = u.info + tag;
		u.tag = u.tag + tag;
	}

	void pushdown(int u) {
		if (tr[u].tag.exist()) {
			pushdown(tr[ls], tr[u].tag);
			pushdown(tr[rs], tr[u].tag);
			tr[u].tag.clear();
		}
	}

	void build(int l, int r, int u = 1) {
		tr[u] = {l, r, {}};
		if (l == r) {
			return;
		}

		int mid = l + r >> 1;
		build(l, mid, ls); build(mid + 1, r, rs);
		pushup(u);
	}

	void build(int l, int r, std::vector<Info> & a, int u = 1) {
		tr[u] = {l, r, {}};
		if (l == r) {
			tr[u].info = a[l];
			return;
		}

		int mid = l + r >> 1;
		build(l, mid, a, ls); build(mid + 1, r, a, rs);
		pushup(u);
	}

	void modify(int l, int r, Tag tag, int u = 1) {
		if (l <= tr[u].l && tr[u].r <= r) {
			pushdown(tr[u], tag);
			return;
		}

		pushdown(u);
		int mid = umid;
		if (l <= mid) {
			modify(l, r, tag, ls);
		}

		if (r > mid) {
			modify(l, r, tag, rs);
		}

		pushup(u);
	}

	Info query(int l, int r, int u = 1) {
		if (l <= tr[u].l && tr[u].r <= r) {
			return tr[u].info;
		}

		pushdown(u);

		int mid = umid;
		if (r <= mid) {
			return query(l, r, ls);
		}  else if (l > mid) {
			return query(l, r, rs);
		}

		return query(l, r, ls) + query(l, r, rs);
	}

	// int query_first_not_appear(int u = 1) {
	// 	if (tr[u].l == tr[u].r) {
	// 		return tr[u].l;
	// 	}

	// 	pushdown(u);
	// 	int mid = umid;
	// 	if (tr[ls].info.sum != tr[ls].info.len) {
	// 		return query_first_not_appear(ls);
	// 	} else {
	// 		return query_first_not_appear(rs);
	// 	}
	// }
};

struct Info {
	i64 max, p;
};

struct Tag {
	i64 add;
	bool exist() {	
		return add != 0;
	}

	void clear() {
		add = 0;
	}
};

Info operator + (const Info & a, const Info & b) {
	Info res{};
	res.max = std::max(a.max, b.max);
	res.p = a.max >= b.max ? a.p : b.p;
	return res;
}

Info operator + (const Info & a, const Tag & b) {
	Info res{};
	res.max = a.max + b.add;
	res.p = a.p;
	return res;
}

Tag operator + (const Tag & a, const Tag & b) {
	Tag res{};
	res.add = a.add + b.add;
	return res;
}

void solve() {
    int n;
    std::cin >> n;
    std::vector<std::array<int, 3>> a(n);
    std::vector<int> b;
    for (int i = 0; i < n; ++ i) {
    	int x, y, c;
    	std::cin >> x >> y >> c;
    	if (x > y) {
    		std::swap(x, y);
    	}
    	b.push_back(x);
    	b.push_back(y);
    	a[i] = {x, y, c};
    }	

    std::ranges::sort(b);
    b.erase(std::unique(b.begin(), b.end()), b.end());
    int m = b.size();
    auto get = [&](int x) -> int {
    	return std::ranges::lower_bound(b, x) - b.begin();
    };

    std::vector<Info> info(m);
    for (int i = 0; i < m; ++ i) {
    	info[i] = {-b[i], i};
    }

    LazySegmentTree<Info, Tag> tr(info);
    std::ranges::sort(a, std::greater<>());
    for (auto & [x, y, c] : a) {
    	x = get(x), y = get(y);
    }

    i64 ans = 0, l = 2e9, r = 2e9;
    for (int i = 0; i < n; ++ i) {
    	auto [x, y, c] = a[i];
    	tr.modify(y, m - 1, Tag{c});
    	if (i + 1 < n && a[i + 1][0] == x) {
    		continue;
    	}

    	auto info = tr.query(x, m - 1);
    	if (b[x] + info.max > ans) {
    		ans = b[x] + info.max;
    		l = b[x], r = b[info.p];
    	}
    }

    std::cout << ans << "\n";
    std::cout << l << " " << l << " " << r << " " << r << "\n";
}
posted @ 2025-05-01 17:27  maburb  阅读(15)  评论(0)    收藏  举报