VP Educational Codeforces Round 67 (Rated for Div. 2)
A. Stickers and Toys
题意:\(n\)个罐子,有\(s\)个贴纸和\(t\)个玩具在里面,一个罐子最多有一个贴纸和一个玩具。求至少开几个罐子才能或者至少一个贴纸和一个玩具。
只需要开\(n - s + 1\)个罐子就能有贴纸,开\(n - t + 1\)个就能有玩具。所以答案是这两个的最大值。
点击查看代码
void solve() {
int n, s, t;
std::cin >> n >> s >> t;
std::cout << n - std::min(s, t) + 1 << "\n";
}
B. Letters Shop
题意:给你一个字符串\(s\),询问若干个字符串\(t\),找\(s\)的一个最短的前缀,使得对于\(t\)出现的每个字母这个前缀的这个字母的出现次数都大于等于\(t\)的这个字母的出现次数。
前缀和预处理字母个数,然后每个询问二分。
点击查看代码
void solve() {
int n;
std::cin >> n;
std::string s;
std::cin >> s;
std::vector cnt(n + 1, std::array<int, 26>{});
for (int i = 0; i < n; ++ i) {
cnt[i + 1] = cnt[i];
cnt[i + 1][s[i] - 'a'] += 1;
}
int m;
std::cin >> m;
while (m -- ) {
std::string t;
std::cin >> t;
std::array<int, 26> cnt1{};
for (auto & c : t) {
++ cnt1[c - 'a'];
}
auto check = [&](int r) -> bool {
for (int i = 0; i < 26; ++ i) {
if (cnt[r][i] < cnt1[i]) {
return false;
}
}
return true;
};
int l = 1, r = n;
while (l < r) {
int mid = l + r >> 1;
if (check(mid)) {
r = mid;
} else {
l = mid + 1;
}
}
std::cout << l << "\n";
}
}
C. Vasya And Array
题意:一个数组,\(m\)个条件,一种是\([l, r]\)是有序的,一种是\([l, r]\)是乱序的。构造一个符合条件的数组。
把有序的\([l, r]\)都拿出来,记\(R[i]\)表示经过一些区间可以覆盖\([i, r]\)的最远的\(r\)。这个可以双层循环求。
然后判断每个乱序的区间是不是被有序区间包含,如果是就无解。
否则就把有序的区间从后往前对于每个区间里的数从小到大放,这样就能保证每个区间有序,且每个区间的对于后面的区间都是无序。这样肯定能满足无序区间的条件。
点击查看代码
void solve() {
int n, m;
std::cin >> n >> m;
std::vector<int> R(n + 1);
std::ranges::iota(R, 0);
std::vector<std::pair<int, int>> a;
for (int i = 0; i < m; ++ i) {
int t, l, r;
std::cin >> t >> l >> r;
if (t == 1) {
R[l] = std::max(R[l], r);
} else {
a.emplace_back(l, r);
}
}
for (int i = 1, pre = 1; i <= n; ++ i) {
int r = std::max(pre, R[i]);
for (int j = i; j <= r; ++ j) {
r = std::max(r, R[j]);
}
R[i] = r;
pre = std::max(pre, R[i]);
}
for (auto & [l, r] : a) {
if (R[l] >= r) {
std::cout << "NO\n";
return;
}
}
std::vector<std::pair<int, int>> b;
for (int i = 1; i <= n; ++ i) {
b.emplace_back(i, R[i]);
i = R[i];
}
std::reverse(b.begin(), b.end());
std::vector<int> ans(n + 1);
int x = 1;
for (auto & [l, r] : b) {
for (int i = l; i <= r; ++ i) {
ans[i] = x ++ ;
}
}
std::cout << "YES\n";
for (int i = 1; i <= n; ++ i) {
std::cout << ans[i] << " \n"[i == n];
}
}
D. Subarray Sorting
题意:给你两个数组\(a, b\),你每次可以给\(a\)的一个子数组排序,求能不能使得\(a\)变成\(b\)。
一段区间排序,可以拆分为两个两个交换,这类似于冒泡排序。所以我们可以每次只给一个长度为\(2\)的区间排序。
那么我们从前往后看,如果\(a\)中和\(b_i\)相等的最近的数想要到\(i\)给位置,那么需要满足它是前面数最小的。于是我们用线段树维护区间最小值,每次找到\(b_i\)就判断它是不是前缀里最小的数,然后把它更改为无穷大,这样相当于把它删去。
点击查看代码
#define ls (u << 1)
#define rs (u << 1 | 1)
#define umid (tr[u].l + tr[u].r >> 1)
template <class Info>
struct Node {
int l, r;
Info info;
};
template <class Info>
struct SegmentTree {
std::vector<Node<Info> > tr;
SegmentTree(int _n) {
init(_n);
}
SegmentTree(std::vector<Info> & a) {
init(a);
}
void init(int _n) {
tr.assign(_n << 2, {});
build(0, _n - 1);
}
void init(std::vector<Info> & a) {
int _n = (int)a.size();
tr.assign(_n << 2, {});
build(0, _n - 1, a);
}
void pushup(int u) {
tr[u].info = tr[ls].info + tr[rs].info;
}
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);
}
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 p, Info add, bool set = false) {
int u = 1;
while (tr[u].l != tr[u].r) {
int mid = umid;
if (p <= mid) {
u = ls;
} else {
u = rs;
}
}
if (set) {
tr[u].info = add;
} else {
tr[u].info = tr[u].info + add;
}
u >>= 1;
while (u) {
pushup(u);
u >>= 1;
}
}
Info query(int l, int r, int u = 1) {
if (l <= tr[u].l && tr[u].r <= r) {
return tr[u].info;
}
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);
}
};
struct Info {
int min;
};
Info operator + (const Info & a, const Info & b) {
Info res{};
res.min = std::min(a.min, b.min);
return res;
}
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];
-- a[i];
}
for (int i = 0; i < n; ++ i) {
std::cin >> b[i];
-- b[i];
}
std::vector<std::queue<int>> q(n);
std::vector<Info> info(n);
for (int i = 0; i < n; ++ i) {
q[a[i]].push(i);
info[i] = {a[i]};
}
const int inf = 1e9;
SegmentTree<Info> tr(info);
for (int i = 0; i < n; ++ i) {
if (q[b[i]].empty()) {
std::cout << "NO\n";
return;
}
int p = q[b[i]].front(); q[b[i]].pop();
if (tr.query(0, p).min != a[p]) {
std::cout << "NO\n";
return;
}
tr.modify(p, Info{inf}, true);
}
std::cout << "YES\n";
}
E. Tree Painting
题意:一个树,选一个根使得所有点的子树和的和最大。
考虑换根\(dp\)。先选一号点为根,得到\(f_u\)表示\(u\)这棵子树的答案。\(f_u = size_u + \sum_v f_v\)
然后换根,对于\(u, v\),把\(v\)换成根,那么要减去\(v\)对\(u\)的贡献得到\(u\)除去\(v\)这棵子树的贡献,然后把\(u\)的贡献加到\(v\)就行。
\(f_v = f_u - f_v - size_v + n - size_v\)。
点击查看代码
void solve() {
int n;
std::cin >> n;
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), size(n);
auto dfs = [&](auto & self, int u, int fa) -> void {
size[u] = 1;
for (auto & v : adj[u]) {
if (v == fa) {
continue;
}
self(self, v, u);
size[u] += size[v];
f[u] += f[v];
}
f[u] += size[u];
};
dfs(dfs, 0, -1);
i64 ans = f[0];
auto dfs1 = [&](auto & self, int u, int fa) -> void {
for (auto & v : adj[u]) {
if (v == fa) {
continue;
}
f[v] = f[u] - f[v] - size[v] + f[v] + n - size[v];
ans = std::max(ans, f[v]);
self(self, v, u);
}
};
dfs1(dfs1, 0, -1);
std::cout << ans << "\n";
}
F. Expected Square Beauty
待补。
G. Gang Up
题意:一个图,有\(k\)个人要到\(1\)号点,一个人在\(t\)时刻到达\(1\)号点则代价为\(t\times c\),一条边在同一时刻有\(a\)个人走则代价为\(d\times a^2\)。求最小代价。
考虑费用流。
那么对于时刻,可以建分层图,对于\(d\times a^2\),可以得到\(a^2 - (a -1)^ 2 = 2\times a - 1\),于是可以建\(k\)条边表示。
其它建边就是每一时刻给原图的边都建一下,然后因为可以停留,所以\(t\)时刻\(i\)点可以无费用到\(i+1\)时刻。
然后是时刻的最大值,每个人最多走\(n-1\)条边,可能等其它\(k-1\)个人都走完再走,直接大一点设为\(n\times k\)。
点击查看代码
template<class T>
struct MinCostFlow {
struct _Edge {
int to;
T cap;
T cost;
_Edge(int to_, T cap_, T cost_) : to(to_), cap(cap_), cost(cost_) {}
};
int n;
std::vector<_Edge> e;
std::vector<std::vector<int>> g;
std::vector<T> h, dis;
std::vector<int> pre;
bool dijkstra(int s, int t) {
dis.assign(n, std::numeric_limits<T>::max());
pre.assign(n, -1);
std::priority_queue<std::pair<T, int>, std::vector<std::pair<T, int>>, std::greater<std::pair<T, int>>> que;
dis[s] = 0;
que.emplace(0, s);
while (!que.empty()) {
T d = que.top().first;
int u = que.top().second;
que.pop();
if (dis[u] != d) {
continue;
}
for (int i : g[u]) {
int v = e[i].to;
T cap = e[i].cap;
T cost = e[i].cost;
if (cap > 0 && dis[v] > d + h[u] - h[v] + cost) {
dis[v] = d + h[u] - h[v] + cost;
pre[v] = i;
que.emplace(dis[v], v);
}
}
}
return dis[t] != std::numeric_limits<T>::max();
}
MinCostFlow() {}
MinCostFlow(int n_) {
init(n_);
}
void init(int n_) {
n = n_;
e.clear();
g.assign(n, {});
}
void addEdge(int u, int v, T cap, T cost) {
g[u].push_back(e.size());
e.emplace_back(v, cap, cost);
g[v].push_back(e.size());
e.emplace_back(u, 0, -cost);
}
std::pair<T, T> flow(int s, int t) {
T flow = 0;
T cost = 0;
h.assign(n, 0);
while (dijkstra(s, t)) {
for (int i = 0; i < n; ++i) {
h[i] += dis[i];
}
T aug = std::numeric_limits<int>::max();
for (int i = t; i != s; i = e[pre[i] ^ 1].to) {
aug = std::min(aug, e[pre[i]].cap);
}
for (int i = t; i != s; i = e[pre[i] ^ 1].to) {
e[pre[i]].cap -= aug;
e[pre[i] ^ 1].cap += aug;
}
flow += aug;
cost += aug * h[t];
}
return std::make_pair(flow, cost);
}
struct Edge {
int from;
int to;
T cap;
T cost;
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.cost = e[i].cost;
x.flow = e[i + 1].cap;
a.push_back(x);
}
return a;
}
};
void solve() {
int n, m, k, c, d;
std::cin >> n >> m >> k >> c >> d;
std::vector<int> a(k);
for (int i = 0; i < k; ++ i) {
std::cin >> a[i];
-- a[i];
}
auto get = [&](int t, int i) -> int {
return t * n + i;
};
const int inf = 1e9;
MinCostFlow<int> f((n + k + 1) * n + 2);
int S = (n + k + 1) * n, T = (n + k + 1) * n + 1;
for (int i = 0; i < m; ++ i) {
int u, v;
std::cin >> u >> v;
-- u, -- v;
for (int t = 0; t < n + k; ++ t) {
for (int i = 1; i <= k; ++ i) {
f.addEdge(get(t, u), get(t + 1, v), 1, d * (2 * i - 1));
f.addEdge(get(t, v), get(t + 1, u), 1, d * (2 * i - 1));
}
}
}
for (int i = 0; i < n; ++ i) {
for (int t = 0; t < n + k; ++ t) {
f.addEdge(get(t, i), get(t + 1, i), inf, 0);
}
}
for (int t = 0; t <= n + k; ++ t) {
f.addEdge(get(t, 0), T, inf, t * c);
}
for (int i = 0; i < k; ++ i) {
f.addEdge(S, get(0, a[i]), 1, 0);
}
auto [flow, cost] = f.flow(S, T);
std::cout << cost << "\n";
}