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";
}