VP Educational Codeforces Round 42 (Rated for Div. 2)
A. Equator
题意:求最前的前缀其和大于等于总和一半。
点击查看代码
void solve() {
int n;
std::cin >> n;
std::vector<int> a(n);
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
}
int sum = std::accumulate(a.begin(), a.end(), 0);
sum = (sum + 1) / 2;
for (int i = 0; i < n; ++ i) {
sum -= a[i];
if (sum <= 0) {
std::cout << i + 1 << "\n";
return;
}
}
}
B. Students in Railway Carriage
题意:长度为\(n\)的数组,有些地方能放东西有些不能。有两种类型的东西分别有\(a, b\)个,两个相同类型的不能相邻,求最多放多少个。
以两个不能放的位置分为一个个区间,每个区间讨论一下。如果区间长度是奇数,则有一个可以多放,应该让数量多的放。
点击查看代码
void solve() {
int n, a, b;
std::cin >> n >> a >> b;
std::string s;
std::cin >> s;
s = "*" + s + "*";
n += 2;
int ans = 0;
for (int i = 0; i + 1 < n && (a || b); ++ i) {
int j = i + 1;
while (s[j] != '*') {
++ j;
}
int cnt = j - i - 1;
if (a < b) {
std::swap(a, b);
}
if (b > 0) {
int k = std::min(b, cnt / 2);
b -= k;
ans += k;
}
if (a > 0) {
int k = std::min(a, cnt - cnt / 2);
a -= k;
ans += k;
}
i = j - 1;
}
std::cout << ans << "\n";
}
C. Make a Square
题意:给你一个\(n\),你要通过删除一些数字把它变成平方数。求删除的数最小。
\(n\)是\(int\)范围内。那么我们直接枚举平方数,然后把两个数转为字符串就行子序列匹配,判断\(n\)是否可以变成这个平方数。
点击查看代码
void solve() {
int n;
std::cin >> n;
std::string s = std::to_string(n);
int m = s.size();
int ans = -1;
for (int i = 1; 1LL * i * i <= n; ++ i) {
std::string t = std::to_string(i * i);
int k = t.size();
int y = 0;
for (int x = 0; x < m; ++ x) {
if (s[x] == t[y]) {
++ y;
}
}
if (y == k) {
if (ans == -1 || m - k < ans) {
ans = m - k;
}
}
}
std::cout << ans << "\n";
}
D. Merge Equals
题意:给你一个数组,每次把个数大于等于两个的数里的最小数取出前两个,并删除第一个,把第二个变成两倍。求最后的数组。
模拟。
用\(set\)记录值和下标,以及用一个\(map\)记录每个数的出现次数。每次操作时如果最小的数个数小于2就加入答案。发现取出最小的两个数,把他们变成两倍插到后面那个数的位置,更新\(set\)和\(map\)。
点击查看代码
void solve() {
int n;
std::cin >> n;
std::vector<int> a(n);
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
}
std::set<std::pair<i64, int>> s;
std::map<i64, int> cnt;
for (int i = 0; i < n; ++ i) {
s.insert({a[i], i});
++ cnt[a[i]];
}
std::set<std::pair<int, i64>> ans;
while (s.size()) {
while (s.size() && cnt[s.begin()->first] < 2) {
ans.insert({s.begin()->second, s.begin()->first});
s.erase(s.begin());
}
if (s.empty()) {
break;
}
auto [v, p1] = *s.begin();
s.erase(s.begin());
auto [_, p2] = *s.begin();
s.erase(s.begin());
cnt[v] -= 2;
s.insert({v * 2, p2});
cnt[v * 2] += 1;
}
std::cout << ans.size() << "\n";
for (auto & it : ans) {
std::cout << it.second << " \n"[it == *ans.rbegin()];
}
}
E. Byteland, Berland and Disputed Cities
题意:数轴上有\(n\)个点,点的类型有三种,要求你给点对之间建边,边权为点的距离,使得删去第二种类型的点或删去第三种类型的点后依然联通。
直接两个点分开讨论,然后在相邻的点依次连边是错误。因为如果是\(PRBP\)这种,分开讨论就是两个\(P\)之间的距离乘二,但其实我们可以先连接两个\(P\),然后中间的点连一边代价更小。
点击查看代码
void solve() {
int n;
std::cin >> n;
std::vector<std::pair<i64, std::string>> a(n);
int L = n, R = -1;
for (int i = 0; i < n; ++ i) {
i64 x;
std::string t;
std::cin >> x >> t;
if (t == "P") {
L = std::min(L, i);
R = std::max(R, i);
}
a[i] = {x, t};
}
auto get = [&](int l, int r) -> i64 {
i64 l1 = 1e18, r1 = -1e18, l2 = 1e18, r2 = -1e18;
for (int i = l; i <= r; ++ i) {
if (a[i].second == "P") {
l1 = std::min(l1, a[i].first);
r1 = std::max(r1, a[i].first);
l2 = std::min(l2, a[i].first);
r2 = std::max(r2, a[i].first);
} else if (a[i].second == "B") {
l1 = std::min(l1, a[i].first);
r1 = std::max(r1, a[i].first);
} else {
l2 = std::min(l2, a[i].first);
r2 = std::max(r2, a[i].first);
}
}
return std::max<i64>(0, r1 - l1) + std::max<i64>(0, r2 - l2);
};
auto work = [&](std::vector<i64> & a, i64 l, i64 r) -> i64 {
if (a.empty()) {
return 0;
}
i64 res = 1e18;
for (int i = 0; i + 1 < a.size(); ++ i) {
res = std::min(res, a[i] - l + r - a[i + 1]);
}
return std::min({res, r - a[0], a.back() - l});
};
if (L >= R) {
std::cout << get(0, n - 1) << "\n";
return;
}
i64 ans = get(0, L) + get(R, n - 1);
for (int i = L; i < R; ++ i) {
int j = i + 1;
std::vector<i64> b, r;
while (a[j].second != "P") {
if (a[j].second == "B") {
b.push_back(a[j].first);
} else {
r.push_back(a[j].first);
}
++ j;
}
i64 x = a[i].first, y = a[j].first;
ans += std::min((y - x) * 2, y - x + work(b, x, y) + work(r, x, y));
i = j - 1;
}
std::cout << ans << "\n";
}
F. Simple Cycles Edges
题意:给你一个图,找有多少边恰好在一个环里。
关于环的问题,我们想到\(tarjan\)求联通分量,通过猜以及观察样例,如果一个点双里只有一个环,那么这个环上的点都满足条件。
那么我们先求点双。那么我们如何知道每个点双里有多少边呢?可以和记录点一样,开一个栈,每次正向搜索的时候把边加入这个栈,那么遇到断点我们就通过弹栈把这些边弹出来就行。
点击查看代码
void solve() {
int n, m;
std::cin >> n >> m;
std::vector<std::vector<std::pair<int, int>>> adj(n);
for (int i = 0; i < m; ++ i) {
int u, v;
std::cin >> u >> v;
-- u, -- v;
adj[u].emplace_back(v, i);
adj[v].emplace_back(u, i);
}
std::vector<int> dfn(n), low(n), id(n), stk(n + 10), stk1(m + 10), cut(n);
int top = 0, top1 = 0, idx = 0, dcc_cnt = 0;
std::vector<std::vector<int>> dcc(n), dcc_edges(n);
int root;
auto tarjan = [&](auto & self, int u, int fa) -> void {
dfn[u] = low[u] = ++ idx;
stk[ ++ top] = u;
int flag = 0;
for (auto & [v, edge_id] : adj[u]) {
if (!dfn[v]) {
stk1[ ++ top1] = edge_id;
self(self, v, u);
low[u] = std::min(low[u], low[v]);
if (dfn[u] <= low[v]) {
if (u != root || ++ flag > 1) {
cut[u] = 1;
}
int x;
do {
x = stk[top -- ];
id[x] = dcc_cnt;
dcc[dcc_cnt].push_back(x);
} while (x != v);
do {
x = stk1[top1 --];
dcc_edges[dcc_cnt].push_back(x);
} while (x != edge_id);
dcc[dcc_cnt].push_back(u);
id[u] = dcc_cnt;
++ dcc_cnt;
}
} else if (v != fa){
low[u] = std::min(low[u], dfn[v]);
if (dfn[v] < dfn[u]) {
stk1[ ++ top1] = edge_id;
}
}
}
};
for (int i = 0; i < n; ++ i) {
if (!dfn[i]) {
if (adj[i].empty()) {
id[i] = dcc_cnt;
dcc[dcc_cnt ++ ].push_back(i);
} else {
root = i;
tarjan(tarjan, i, -1);
}
}
}
std::vector<int> ans;
for (int i = 0; i < dcc_cnt; ++ i) {
if (dcc[i].size() == dcc_edges[i].size()) {
for (auto & j : dcc_edges[i]) {
ans.push_back(j);
}
}
}
std::sort(ans.begin(), ans.end());
std::cout << ans.size() << "\n";
for (auto & x : ans) {
std::cout << x + 1 << " ";
}
std::cout << "\n";
}