VP Educational Codeforces Round 158 (Rated for Div. 2)
A. Line Trip
题意:你要从\(0\)到\(x\),然后再从\(x\)到\(0\),路上有一些加油站,每行驶一单位消耗一格油,到了加油家会加满油,问油箱至少得多大。
显然油箱要支撑我们行驶完任意两个相邻点之间的路程,注意最后\(x\)到\(a_n\)的路径因为要返回所以要算两遍。
点击查看代码
void solve() {
int n, x;
std::cin >> n >> x;
std::vector<int> a(n);
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
}
int max = std::max(a[0], 2 * (x - a.back()));
for (int i = 1; i < n; ++ i) {
max = std::max(max, a[i] - a[i - 1]);
}
std::cout << max << "\n";
}
B. Chip and Ribbon
题意:n个数,每次你可以选择一段区间让这个区间的数都加\(1\),问最少几次可以让每个数恰好是目标值。
考虑一段升序的区间\([l, r]\)要选几次,显然是最大值,那么对于这个区间后面的数可以被我们拉进来选几次,对于后面的一个\(i\),显然它可以被影响\(\min\{a_{l+1}, ... a_i\}\)次,那么我们枚举每一个升序的连续段,记录每个位置前面的升序段操作数对他的影响,然后在每个升序段的最大值处累加答案就行。
点击查看代码
void solve() {
int n;
std::cin >> n;
std::vector<i64> a(n);
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
}
a.push_back(0);
i64 ans = 0, min = 0;
for (int i = 0; i < n; ++ i) {
if (a[i] > a[i + 1]) {
ans += a[i] - min;
min = a[i + 1];
}
}
std::cout << ans - 1 << "\n";
}
C. Add, Divide and Floor
题意:给你一个数组,每次选择一个数\(x\)让数组里每个数都加\(x\)后向下取整,问最少几次能让数组里的数都相等。
因为所有数都是进行同样的操作,那么我们应该关注最大值变成目标值需要几次,一个可行方案是都变成\(0\),这样的操作数是\(log_{max}+1\),但我们注意到如果每次可以维持\(min\)不变,那么\(max\)变成\(min\)只需要\(log_{max-min}+1\)次,如何维持\(min\)不变?我们每次都选\(x\) = \(min\)就行了。
点击查看代码
void solve() {
int n;
std::cin >> n;
std::vector<int> a(n);
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
}
int min = *std::min_element(a.begin(), a.end());
int max = *std::max_element(a.begin(), a.end());
int ans = 0;
while (max != min) {
max = max + min >> 1;
++ ans;
}
std::cout << ans << "\n";
if (ans <= n) {
for (int i = 1; i <= ans; ++ i) {
std::cout << min << " \n"[i == ans];
}
}
}
D. Yet Another Monster Fight
题意:有\(n\)个怪物,你可以选一个怪物作为目标,如何它会受到\(x\)点伤害,然后每次都会随机往左边或者右边扩展,第\(i\)次扩展到的怪物会受到\(x-i\)点伤害。要一次杀死所有怪物,让你选目标,\(x\)最小可以是多少。
假设我们选择了\(i\)为目标,那么对于\(j(j>i)\)的怪物,最差是一直把左边打完再走右边,受到的最小可能伤害是\(x-j-1\),如果是\(j(j<i)\),那么会受到\(x-(n-j)\)点伤害。那么具体的,如果\(i\)是受击目标,它会受到\(x\)的伤害,那么\(x\)要大于等于\(a_i\),如果\(i\)在目标右边,会受到\(x-j-1\)伤害,得\(x >= a_i + j + 1\),如果在左边,可以得\(x >= a_i + n - j\)。那么我们记录每个区间里每个点作为右边点得最大值,和作为左边的点的最大值,就可以枚举目标位置三个情况里取最大值。
发现这两个区间一定是前缀或后缀,可以一个数组维护,但我赛时没想到,直接上了线段树。
点击查看代码
#define ls (u << 1)
#define rs (u << 1 | 1)
struct Node {
int l, r;
int max;
};
struct SegmentTree {
std::vector<Node> tr;
SegmentTree(int _n) {
tr.assign(_n << 2, {});
build(1, 1, _n);
}
void pushup(int u) {
tr[u].max = std::max(tr[ls].max, tr[rs].max);
}
void build(int u, int l, int r) {
tr[u] = {l, r};
int mid = l + r >> 1;
if (l == r) {
return;
}
build(ls, l, mid); build(rs, mid + 1, r);
}
void modify(int u, int p, int v) {
if (tr[u].l == tr[u].r) {
tr[u].max = v;
return;
}
int mid = tr[u].l + tr[u].r >> 1;
if (p <= mid) {
modify(ls, p, v);
} else {
modify(rs, p, v);
}
pushup(u);
}
int query(int u, int l, int r) {
if (l <= tr[u].l && tr[u].r <= r) {
return tr[u].max;
}
int mid = tr[u].l + tr[u].r >> 1;
if (r <= mid) {
return query(ls, l, r);
} else if (l > mid) {
return query(rs, l, r);
} else {
return std::max(query(ls, l, r), query(rs, l, r));
}
}
};
void solve() {
int n;
std::cin >> n;
std::vector<int> a(n);
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
}
SegmentTree trl(n), trr(n);
for (int i = 0; i < n; ++ i) {
trl.modify(1, i + 1, a[i] + n - i - 1);
trr.modify(1, i + 1, a[i] + i);
}
int ans = 2e9;
for (int i = 1; i <= n; ++ i) {
if (i == 1) {
ans = std::min(ans, std::max(a[i - 1], trr.query(1, i + 1, n)));
} else if (i == n) {
ans = std::min(ans, std::max(a[i - 1], trl.query(1, 1, i - 1)));
} else {
ans = std::min(ans, std::max({a[i - 1], trr.query(1, i + 1, n), trl.query(1, 1, i - 1)}));
}
}
std::cout << ans << "\n";
}
E. Compressed Tree
题意:有一颗树,每个节点有权值,你可以每次选择一个叶子删掉,在你操作完之后,会删除所有有恰好两条边的点,问最后剩下的点权值和最大是多少。
考虑\(dp\), \(f_u\)表示\(u\)这棵子树里可以取到的最大值是多少,那么对于叶子就是\(f_u = a_u\),对于非叶节点,我们把所有子节点的\(f\)弄出来,那么我们可以只留下\(u\)这个点,值是\(a_u\),也可以只留下一个最大的子节点。也可以选择两个以上子节点,那么\(a_u\)必须选,我们选完两个最大子节点后再把其他大于零的节点也加上就行了。那么\(f_u\)就是这几个情况的最大值。
那么求出来了\(f_u\),如何统计答案?统计单个点,两个点,和一颗子树这三种情况的最大值就行了,注意统计子树时,我们不需要管父亲节点,我当时就是想着如何把父亲节点的贡献加进来,写了半天。如果我们选的点要往上走,那么总有一个祖先节点可以枚举到这种情况,所以我们只要管每个节点的子节点就行了。 注意在\(u\)这个子节点里,如果只选两个子节点,那么\(u\)这个点会被删除,不能算进答案。
点击查看代码
void solve() {
int n;
std::cin >> n;
std::vector<i64> a(n);
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
}
std::vector<std::vector<int> > adj(n);
for (int i = 1; i < n; ++ i) {
int u, v;
std::cin >> u >> v;
-- u, -- v;
adj[u].push_back(v);
adj[v].push_back(u);
}
std::vector<i64> f(n, 0);
i64 ans = 0;
auto dfs = [&](auto self, int u, int fa) -> void {
std::vector<i64> b;
f[u] = a[u];
ans = std::max(ans, a[u]);
for (auto & v : adj[u]) {
if (v == fa) {
continue;
}
self(self, v, u);
b.push_back(f[v]);
ans = std::max(ans, a[u] + f[v]);
}
std::sort(b.begin(), b.end(), std::greater<i64>());
if (b.size() == 1) {
f[u] = std::max(a[u], b[0]);
} else if (b.size() >= 2) {
i64 sum = b[0] + b[1];
f[u] = std::max({a[u], b[0], b[1], sum + a[u]});
ans = std::max(ans, sum);
for (int i = 2; i < b.size(); ++ i) {
sum += b[i];
f[u] = std::max(f[u], sum + a[u]);
ans = std::max(ans, sum + a[u]);
}
}
};
dfs(dfs, 0, -1);
std::cout << ans << "\n";
}

浙公网安备 33010602011771号