VP Educational Codeforces Round 55 (Rated for Div. 2)
A. Vasya and Book
点击查看代码
void solve() {
int n, x, y, d;
std::cin >> n >> x >> y >> d;
if (std::abs(x - y) % d) {
int ans = 1e9;
if ((y - 1) % d == 0) {
ans = std::min(ans, (x + d - 1) / d + (y - 1) / d);
}
if ((n - y) % d == 0) {
ans = std::min(ans, (n - x + d - 1) / d + (n - y) / d);
}
if (ans == 1e9) {
std::cout << -1 << "\n";
} else {
std::cout << ans << "\n";
}
} else {
std::cout << std::abs(x - y) / d << "\n";
}
}
B. Vova and Trophies
题意:给你一个只包含两种字符的串,操作恰好一次:交换两个字符。使得连续的G最长。
二分长度,然后看前缀和里是不是G的数量大于等于\(len\)或者等于\(len-1\)且外面还有G。
点击查看代码
void solve() {
int n;
std::cin >> n;
std::string s;
std::cin >> s;
int sum = std::ranges::count(s, 'G');
std::vector<int> pre(n + 1);
for (int i = 0; i < n; ++ i) {
pre[i + 1] = pre[i] + (s[i] == 'G');
}
auto check = [&](int len) -> bool {
for (int i = 1; i + len - 1 <= n; ++ i) {
int cnt = pre[i + len - 1] - pre[i - 1];
if (cnt >= len || cnt == len - 1 && cnt < sum) {
return true;
}
}
return false;
};
int l = 0, r = n;
while (l < r) {
int mid = l + r + 1 >> 1;
if (check(mid)) {
l = mid;
} else {
r = mid - 1;
}
}
std::cout << l << "\n";
}
C. Multi-Subject Competition
题意:\(n\)个人,\(m\)个类型,第\(i\)个人类型为\(s_i\),能力为\(r_i\)。你需要选一些人出来,使得选出来的每种类型的人数相同。求能力总和最大。
每个类型的人搞个前缀和\(sum[i]\),然后依次把\(sum[i][j]\)加到\(ans[j]\)里。那么答案直接枚举得到最大的\(ans\)。
点击查看代码
void solve() {
int n, m;
std::cin >> n >> m;
std::vector<std::vector<int>> a(m);
for (int i = 0; i < n; ++ i) {
int s, r;
std::cin >> s >> r;
-- s;
a[s].push_back(r);
}
std::vector<int> sum(n);
for (int i = 0; i < m; ++ i) {
std::ranges::sort(a[i], std::greater<int>());
for (int j = 0, pre = 0; j < a[i].size(); ++ j) {
pre += a[i][j];
sum[j] += std::max(0, pre);
}
}
int ans = 0;
for (int i = 0; i < n; ++ i) {
ans = std::max(ans, sum[i]);
}
std::cout << ans << "\n";
}
D. Maximum Diameter Graph
题意:构造一个图,使得每个点的度数小于等于\(a_i\)。且直径最大。
先构造直径,显然是一条链。把所有\(a_i \geq 2\)的点连到一起,所有\(a_i = 1\)的点,分两个到两端,然后剩下的和其它点连就行了。看能不能连完。
点击查看代码
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> b, c;
for (int i = 0; i < n; ++ i) {
if (a[i] >= 2) {
b.push_back(i);
} else {
c.push_back(i);
}
}
if (b.empty()) {
std::cout << "NO\n";
return;
}
std::vector<std::pair<int, int>> ans;
std::vector<int> d(n);
for (int i = 0; i + 1 < b.size(); ++ i) {
ans.emplace_back(b[i], b[i + 1]);
++ d[b[i]];
++ d[b[i + 1]];
}
int ans_len = (int)b.size() - 1;
if (c.size()) {
ans.emplace_back(b.back(), c.back());
++ d[c.back()];
++ d[b.back()];
c.pop_back();
++ ans_len;
}
if (c.size()) {
++ ans_len;
}
for (int i = 0; i < b.size() && c.size(); ++ i) {
while (d[b[i]] < a[b[i]] && c.size()) {
ans.emplace_back(b[i], c.back());
++ d[c.back()];
++ d[b[i]];
c.pop_back();
}
}
if (c.size()) {
std::cout << "NO\n";
return;
} else {
std::cout << "YES " << ans_len << "\n";
std::cout << ans.size() << "\n";
for (auto & [u, v] : ans) {
std::cout << u + 1 << " " << v + 1 << "\n";
}
}
}
E. Increasing Frequency
题意:给你一个长度为\(n\)的数组\(a\)和一个数字\(c\)。你要恰好操作一个区间\([l, r]\)把其中所有数加上某个数。求\(c\)的最大数量。
其实就是找一个区间,把区间里某一类数的个数减去区间\(c\)出现的个数,求最大。
如果我们记\(sum[x][i]\)为数字\(x\)在\([1, i]\)里出现的次数,那么对于\(i\)这个位置,我们肯定要找一个\(j, (j < i)\)使得\(a_j = x\)。因为如果左端点不是\(x\)显然可以截掉。那么这个位置的最大值是\(\max_{j, a_j = x} sum[x][i] - sum[x][j - 1] - (sum[c][i] - sum[c][j - 1])\),得到\(sum[x][i] - sum[c][i] + (sum[c][j - 1] - sum[x][j - 1])\)。发现\(i, j\)可以拆开,那么我们只需要记录\(sum[c][j - 1] - sum[c][j - 1]\)的最大值就行。于是也不用真的开二维数组记录前缀和。
点击查看代码
void solve() {
int n, c;
std::cin >> n >> c;
const int N = 5e5 + 5;
std::vector<int> a(n + 1);
for (int i = 1; i <= n; ++ i) {
std::cin >> a[i];
}
std::vector<int> pre(n + 1);
for (int i = 1; i <= n; ++ i) {
pre[i] = pre[i - 1] + (a[i] == c);
}
std::vector<int> max(N), cnt(N);
int ans = 0;
for (int i = 1; i <= n; ++ i) {
max[a[i]] = std::max(max[a[i]], pre[i - 1] - cnt[a[i]]);
++ cnt[a[i]];
ans = std::max(ans, cnt[a[i]] - pre[i] + max[a[i]]);
}
std::cout << std::ranges::count(a, c) + ans << "\n";
}
F. Speed Dial
题意:给你\(n\)个数字串,每个数字串需要出现\(m_i\)次。你可以选择\(k\)个数字串,每次可以不费代价选择其中一个(也可以选择空串),使得当前的数字串等于它,然后每次你可以花费\(1\)的代价把任意一个字符接到后面。如果当前串等于\(n\)个数字串其中一个,你可以使这个数字串出现次数加一,然后你的数字串会变成空串。求所有数字串满足条件的最小代价。
我们建一个\(trie\),叶子的值为以他结尾的串的\(m\)的和。那么问题变成,在\(trie\)上选择\(k + 1\)个节点,其中根节点必选。每个叶子节点的代价为到最近的一个被选定节点的距离$\times $ 这棵叶子的值。总代价就是叶子代价的和。求总代价最小。
考虑用\(dp\)求解,记\(f[u][i][j]\)为\(u\)这个节点距离最近的被选择的点距离为\(i\),\(u\)这棵子树已经选了\(j\)个节点的最小代价。如果\(i=0\)代表选择了\(u\),这时\(j \geq 1\)。那么叶子节点初始化是:如果选择这个点\(f[u][0][1] = 0\),否则枚举\(i\),\(f[u][i][0] = i\times val_u\)。
对于非叶子节点,\(f[u][i][j] += \min_{x = 0}^{k+1} \min(f[u][i][j - x] + f[v][i + 1][x], f[u][i][j-x] + f[v][0][x])\),意味\(v\)这个子节点选了\(x\)个,是不是和\(u\)选同一个节点,或者选择自己。
注意一些细节就行。
点击查看代码
const int TrieN = 505;
int trie[TrieN][10];
int node_val[TrieN];
struct Trie {
int idx;
Trie() {
idx = 0;
newNode();
}
int newNode() {
memset(trie[idx], 0, sizeof trie[idx]);
node_val[idx] = 0;
return idx ++ ;
}
void insert(std::string s, int val) {
int p = 0;
for (auto & c : s) {
int x = c - '0';
if (!trie[p][x]) {
trie[p][x] = newNode();
}
p = trie[p][x];
}
node_val[p] += val;
}
int dp(int k) {
std::vector f(idx, std::vector(idx, std::vector<int>(k + 1)));
auto dfs = [&](auto & self, int u, int dep) -> void {
f[u][0][1] = 0;
for (int i = 1; i <= dep; ++ i) {
f[u][i][0] = i * node_val[u];
}
for (int i = 0; i <= 9; ++ i) {
int v = trie[u][i];
if (!v) {
continue;
}
self(self, v, dep + 1);
for (int j = 0; j <= dep; ++ j) {
for (int l = k; l >= !j; -- l) {
int min = 1e9;
for (int r = 0; r <= l - !j; ++ r) {
min = std::min(min, f[u][j][l - r] + f[v][j + 1][r]);
if (r > 0) {
min = std::min(min, f[u][j][l - r] + f[v][0][r]);
}
}
f[u][j][l] = min;
}
}
}
};
dfs(dfs, 0, 0);
return f[0][0][k];
}
};
void solve() {
int n, k;
std::cin >> n >> k;
Trie tr;
for (int i = 0; i < n; ++ i) {
std::string s;
int m;
std::cin >> s >> m;
tr.insert(s, m);
}
std::cout << tr.dp(k + 1) << "\n";
}
G. Petya and Graph
题意:给你一个图,选择其中一个子图,其价值为边权减点权。如果一条边在子图里,那么它的两个端点也在子图里。
最大权闭合子图板子。
算法:原图边容量为正无穷,虚拟源点向正权点连边,负权点向虚拟汇点连边,容量为点权绝对值
最大权闭合图 = 所有正权点之和 – 最小割
点击查看代码
constexpr i64 inf = 1E18;
template<class T>
struct MaxFlow {
struct _Edge {
int to;
T cap;
_Edge(int to, T cap) : to(to), cap(cap) {}
};
int n;
std::vector<_Edge> e;
std::vector<std::vector<int>> g;
std::vector<int> cur, h;
MaxFlow() {}
MaxFlow(int n) {
init(n);
}
void init(int n) {
this->n = n;
e.clear();
g.assign(n, {});
cur.resize(n);
h.resize(n);
}
bool bfs(int s, int t) {
h.assign(n, -1);
std::queue<int> que;
h[s] = 0;
que.push(s);
while (!que.empty()) {
const int u = que.front();
que.pop();
for (int i : g[u]) {
auto [v, c] = e[i];
if (c > 0 && h[v] == -1) {
h[v] = h[u] + 1;
if (v == t) {
return true;
}
que.push(v);
}
}
}
return false;
}
T dfs(int u, int t, T f) {
if (u == t) {
return f;
}
auto r = f;
for (int &i = cur[u]; i < int(g[u].size()); ++i) {
const int j = g[u][i];
auto [v, c] = e[j];
if (c > 0 && h[v] == h[u] + 1) {
auto a = dfs(v, t, std::min(r, c));
e[j].cap -= a;
e[j ^ 1].cap += a;
r -= a;
if (r == 0) {
return f;
}
}
}
return f - r;
}
void addEdge(int u, int v, T c) {
g[u].push_back(e.size());
e.emplace_back(v, c);
g[v].push_back(e.size());
e.emplace_back(u, 0);
}
T flow(int s, int t) {
T ans = 0;
while (bfs(s, t)) {
cur.assign(n, 0);
ans += dfs(s, t, std::numeric_limits<T>::max());
}
return ans;
}
std::vector<bool> minCut() {
std::vector<bool> c(n);
for (int i = 0; i < n; i++) {
c[i] = (h[i] != -1);
}
return c;
}
struct Edge {
int from;
int to;
T cap;
T flow;
};
std::vector<Edge> edges() {
std::vector<Edge> a;
for (int i = 0; i < e.size(); i += 2) {
Edge x;
x.from = e[i + 1].to;
x.to = e[i].to;
x.cap = e[i].cap + e[i + 1].cap;
x.flow = e[i + 1].cap;
a.push_back(x);
}
return a;
}
};
void solve() {
int n, m;
std::cin >> n >> m;
std::vector<int> a(n);
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
}
const i64 inf = std::numeric_limits<i64>::max();
MaxFlow<i64> f(n + m + 2);
int s = n + m, t = n + m + 1;
for (int i = 0; i < n; ++ i) {
f.addEdge(i, t, a[i]);
}
i64 sum = 0;
for (int i = 0; i < m; ++ i) {
int u, v, w;
std::cin >> u >> v >> w;
-- u, -- v;
f.addEdge(s, n + i, w);
f.addEdge(n + i, u, inf);
f.addEdge(n + i, v, inf);
sum += w;
}
std::cout << sum - f.flow(s, t) << "\n";
}