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


A. Regular Bracket Sequence

题意:四种括号序列:"((", "()", ")(", "))"。给出每一种的个数,求能不能拼成一个合法的括号序列。

第一种和最后一种个数要相同,同时如果有第三种括号,那么第一种和最后一种个数不能为零。

点击查看代码
void solve() {
    int a, b, c, d;
    std::cin >> a >> b >> c >> d;
    if (a != d || (a == 0 && c != 0)) {
    	std::cout << 0 << "\n";
    } else {
    	std::cout << 1 << "\n";
    }
}

B. Discounts

题意:题意,\(n\)个价值为\(a_i\)的物品,你有\(m\)个优惠卷,可以让第\(q_i\)大的免费,求用每个优惠价的最小价值。

排序后从大到小删除第\(i\)个就是\(q_i\)可以得到的价值。

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

    std::vector<int> st(n, -1);
    int m;
    std::cin >> m;
    for (int i = 0; i < m; ++ i) {
    	int x;
    	std::cin >> x;
    	-- x;
    	st[x] = i;
    }

    i64 sum = std::accumulate(a.begin(), a.end(), 0ll);
    std::ranges::sort(a, std::greater<int>());
   	std::vector<i64> ans(m);
    for (int i = 0; i < n; ++ i) {
    	if (st[i] != -1) {
    		ans[st[i]] = sum - a[i];
    	}
    }

    for (int i = 0; i < m; ++ i) {
    	std::cout << ans[i] << "\n";
    }
}

C. Painting the Fence

题意:\(n\)个区间,求删除两个区间可以覆盖到的点的最大数量。

枚举删除的两个区间就行,用差分求出每个点被区间覆盖的数量,那么这两个区间会减少其对应区间被覆盖一个区间的点和它们的交集被两个区间覆盖的点。

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

    std::vector<int> d(n + 2);
    for (auto & [l, r] : a) {
    	++ d[l];
    	-- d[r + 1];
    }

    std::vector<int> sum1(n + 1), sum2(n + 1);
    int tot = 0;
    for (int i = 1; i <= n; ++ i) {
    	d[i] += d[i - 1];
    	tot += d[i] > 0;
    	sum1[i] = sum1[i - 1];
    	sum2[i] = sum2[i - 1];
    	if (d[i] == 1) {
    		sum1[i] += 1;
    	} else if (d[i] == 2) {
    		sum2[i] += 1;
    	}
    }

    int ans = 0;
    for (int i = 0; i < q; ++ i) {
    	for (int j = i + 1; j < q; ++ j) {
    		auto & [l1, r1] = a[i];
    		auto & [l2, r2] = a[j];
    		int sum = sum1[r1] - sum1[l1 - 1] + sum1[r2] - sum1[l2 - 1];
    		int l = std::max(l1, l2), r = std::min(r1, r2);
    		if (l <= r) {
    			sum += sum2[r] - sum2[l - 1];
    		}

    		ans = std::max(ans, tot - sum);
    	}
    }

    std::cout << ans << "\n";
}

D. Stressful Training

题意:\(n\)个人,每个人电脑一开始有\(a_i\)的电量,每分钟结束消耗\(b_i\)的电量,每分钟结束你可以给一个电脑充\(x\)的电量,求\(k\)分钟不让任何一个电脑电量为负数的最小的\(x\)

考虑二分。
那么\(check\)里可以用小根堆维护一个变成负数的时间、每分钟消耗的电量、 当前电量、 上次充电的时间。那么每次取出来看变成负数的时间是不是小于当前时间,如果是就无解。否则尝试充电,看能不能变回大于等于\(0\)。然后重新计算变成负数的时间,更新电量和充电的时间再入队。

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

    for (int i = 0; i < n; ++ i) {
    	std::cin >> b[i];
    }

    auto check = [&](i64 x) -> bool {
    	using A = std::array<i64, 4>;
    	std::priority_queue<A, std::vector<A>, std::greater<A>> heap;
    	for (int i = 0; i < n; ++ i) {
    		heap.push(std::array<i64, 4>{a[i] / b[i] + 1, -b[i], a[i], 0});
    	}

    	for (int i = 1; i <= k && heap.size(); ++ i) {
    		auto [t, b, a, last] = heap.top(); heap.pop();
    		b = -b;
    		// std::cout << t << " " << a << " " << b << " " << last << "\n";
    		if (i > t) {
    			return false;
    		}

    		a -= (i - last) * b;
    		a += x;
    		if (i != k && a < 0) {
    			return false;
    		}

    		if (i + a / b + 1 <= k) {
    			heap.push(std::array<i64, 4>{i + a / b + 1, -b, a, i});
    		}
    	}

    	return true;
    };

    i64 l = 0, r = 1e13;
    while (l < r) {
    	i64 mid = l + r >> 1ll;
    	if (check(mid)) {
    		r = mid;
    	} else {
    		l = mid + 1;
    	}
    }

    if (!check(l)) {
    	std::cout << -1 << "\n";
    	return;
    }

    std::cout << l << "\n";
}

E. Knapsack

题意:背包问题,但容量极大,物品极多,但重量在\([1, 8]\)。求最大重量。

直接背包,显然不行。
考虑贪心后再背包,因为容量有这么大,显然有很多物品是一定在里面的。我们可以取\(lcm\{{1, 2, 3, 4, 5, 6, 7, 8\}} = 840\),第\(i\)个物品最多留\(\frac{840}{i}\)个,其余的都贪心取掉。那么可以进行一个\(O(8 \times 840 \times 8 \times \sum_{i=1}^{8} \frac{840}{i})\)的背包\(dp\)求出剩下物品得到\(i \in [1, 840 \times 8]\)的可行性。

点击查看代码
void solve() {
    i64 w;
    std::cin >> w;
    i64 cnt[9]{};
    i64 sum = 0;
    for (int i = 1; i <= 8; ++ i) {
    	std::cin >> cnt[i];
    	sum += cnt[i] * i;
    }

    if (sum <= w) {
    	std::cout << sum << "\n";
    	return;
    }

    int left[9]{};
    for (int i = 1; i <= 8; ++ i) {
    	left[i] = std::min(cnt[i], 840ll / i);
    	cnt[i] -= left[i];
    }

    i64 ans = 0, tot = std::max(0ll, w - 840);
    for (int i = 1; i <= 8; ++ i) {
    	i64 t = std::min(cnt[i], (tot - ans) / i);
    	ans += t * i;
    }

    bool f[9][840 * 8 + 10]{};
    f[0][0] = true;
    for (int i = 1; i <= 8; ++ i) {
    	for (int j = 0; j <= 840 * 8; ++ j) {
    		for (int k = 0; k <= left[i] && j >= i * k; ++ k) {
    			f[i][j] |= f[i - 1][j - i * k];
    		}
    	}
    }

    for (int i = w - ans; i >= 0; -- i) {
    	if (f[8][i]) {
    		std::cout << ans + i << "\n";
    		return;
    	}
    }
}

F. Clear the String

题意:一个字符串,每次删除一个子串,满足这个子串的字符都相等。求最少删除次数把字符串删完。

区间\(dp\),考虑\(f[i][j]\)表示删除\([i, j]\)的最小次数。那么\(f[i][i] = 1\)\(f[i][j] = \min(f[i + 1][j], f[i][j - 1])\),意味直接删除当前一个字符,然后\(f[i][j] = \min_{k=i + 1}^{j} [s_i == s_k](f[i + 1][k - 1] + f[k][j])\)。意味把\([i + 1, k + 1]\)删完后可以把\(i, k\)看作一个字符。

点击查看代码
void solve() {
    int n;
    std::cin >> n;
    std::string s;
    std::cin >> s;
    std::vector f(n + 1, std::vector<int>(n + 1));
    for (int len = 1; len <= n; ++ len) {
    	for (int i = 1; i + len - 1 <= n; ++ i) {
    		int j = i + len - 1;
    		if (len == 1) {
    			f[i][j] = 1;
    		} else {
    			f[i][j] = std::min(f[i + 1][j] + 1, f[i][j - 1] + 1);
    			for (int k = i + 1; k <= j; ++ k) {
    				if (s[i - 1] == s[k - 1]) {
    					f[i][j] = std::min(f[i][j], f[i + 1][k - 1] + f[k][j]);
    				}
    			}
    		}
    	}
    }

    std::cout << f[1][n] << "\n";
}

G. Greedy Subsequences

题意:给你一个数组,求每个长度为\(k\)的最长上升子序列,其中每个数只能选后面第一个大于自己的数。

由于每个数选的数是固定的,那么可以建成一个森林,然后我们给第\(n + 1\)个赋一个极大值,那么森林就变成了一棵树,第\(n+1\)个点就是根。
那么第\(i\)个位置的答案就是起点大于等于\(i - k + 1\)的点到达不超过\(i\)的最长上升子序列的长度。我们可以从左往右一个点一个点加入,这样上升子序列都不会超过\(i\)。然后把\(i-k\)这个位置的贡献删去就行。每个点的贡献相当于给它的子数的每个点的值加一。我们可以用\(dfs\)序把子树映射为数组,用线段树维护。

点击查看代码
#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;
	Info() {
		max = 0;
	}
};

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);
	return res;
}

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

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

void solve() {
    int n, k;
    std::cin >> n >> k;
    std::vector<int> a(n);
    for (int i = 0; i < n; ++ i) {
    	std::cin >> a[i];
    	-- a[i];
    }

    a.push_back(n);

    std::vector<std::vector<int>> adj(n + 1);
    std::stack<int> stk;
    for (int i = 0; i <= n; ++ i) {
    	while (stk.size() && a[stk.top()] < a[i]) {
    		adj[i].push_back(stk.top());
    		stk.pop();
    	}

    	stk.push(i);
    }

    std::vector<int> in(n + 1), out(n + 1);
    int dfn = 0;
    auto dfs = [&](auto & self, int u) -> void {
    	in[u] = dfn ++ ;
    	for (auto & v : adj[u]) {
    		self(self, v);
    	}

    	out[u] = dfn - 1;
    };

    dfs(dfs, n);

    const i64 inf = 1e9;
    LazySegmentTree<Info, Tag> tr(n + 1);
    for (int i = 0; i < n; ++ i) {
    	tr.modify(in[i], out[i], Tag{1});
    	if (i >= k) {
    		tr.modify(in[i - k], out[i - k], Tag{-inf});
    	}

    	if (i >= k - 1) {
    		std::cout << tr.query(0, n).max << " \n"[i == n - 1];
    	}
    }
}
posted @ 2025-04-17 16:58  maburb  阅读(16)  评论(0)    收藏  举报