VP Educational Codeforces Round 31
A. Book Reading
点击查看代码
void solve() {
int n, t;
std::cin >> n >> t;
std::vector<int> a(n);
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
}
for (int i = 0; i < n; ++ i) {
t -= 86400 - a[i];
if (t <= 0) {
std::cout << i + 1 << "\n";
return;
}
}
}
B. Japanese Crosswords Strike Back
点击查看代码
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];
}
for (int i = 0; i < n; ++ i) {
x -= a[i] + 1;
}
++ x;
if (x != 0) {
std::cout << "NO\n";
} else {
std::cout << "YES\n";
}
}
C. Bertown Subway
题意:\(p_i\)向\(i\)连边,每个点都有一个入度和一个出度。你可以交换一次\(p_i, p_j\)。最后结果就是每个联通块的个数的平方之和。
直接找环,按环里点的个数排序,把最大和次大合并就行。
点击查看代码
void solve() {
int n;
std::cin >> n;
std::vector<int> p(n);
for (int i = 0; i < n; ++ i) {
std::cin >> p[i];
-- p[i];
}
std::vector<int> st(n);
std::vector<int> a;
for (int i = 0; i < n; ++ i) {
if (!st[i]) {
int j = i;
int cnt = 0;
while (!st[j]) {
++ cnt;
st[j] = 1;
j = p[j];
}
a.push_back(cnt);
}
}
if (a.size() <= 1) {
std::cout << (i64)n * n << "\n";
} else {
std::sort(a.begin(), a.end());
i64 ans = 0;
int m = a.size();
for (int i = 0; i + 2 < m; ++ i) {
ans += (i64)a[i] * a[i];
}
ans += (i64)(a[m - 1] + a[m - 2]) * (a[m - 1] + a[m - 2]);
std::cout << ans << "\n";
}
}
D. Boxes And Balls
题意:有\(n\)个球,每个球有\(a_i\)个。每次丢掉两类或者一类球,每次操作都要消耗当前拥有的球的总和。求只剩一类球时的最小花费。
如果反过来考虑,就变成了每次选两个或者三个合并,求最小。
那么就是经典的贪心问题,根据哈夫曼树,我们应该每次选最小的三个合并,如果\(n\)是偶数,应该加一个零进去。
点击查看代码
void solve() {
int n;
std::cin >> n;
std::vector<int> a(n);
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
}
std::priority_queue<i64, std::vector<i64>, std::greater<i64>> heap;
for (int i = 0; i < n; ++ i) {
heap.push(a[i]);
}
if (n % 2 == 0) {
heap.push(0);
}
i64 ans = 0;
while (heap.size() > 1) {
i64 x = heap.top(); heap.pop();
i64 y = heap.top(); heap.pop();
i64 z = heap.top(); heap.pop();
ans += x + y + z;
heap.push(x + y + z);
}
std::cout << ans << "\n";
}
E. Binary Matrix
题意:给你一个\(n\times m\)的\(01\)矩阵,求其中\(1\)的联通块的个数。但空间只有\(16mb\)。
显然无法把矩阵存下来,只能一行一行处理。
考虑使用并查集维护联通块,我们记录上一行每个\(1\)所在的联通块,然后对于当前行的每个\(1\),首先算它们都单独在一个联通块,然后每次和左边已经上一行相同的位置合并,每合并一次联通块数就减一。
点击查看代码
const int N = 1 << 14;
int fa[N << 1];
int last[N], cur[N];
int find(int x) {
return x == fa[x] ? x : fa[x] = find(fa[x]);
}
int merge(int x, int y) {
x = find(x), y = find(y);
if (x == y) {
return 0;
}
fa[y] = x;
return 1;
}
void solve() {
int n, m;
std::cin >> n >> m;
auto get = [&](char c) -> int {
if (c >= 'A') {
return c - 'A' + 10;
}
return c - '0';
};
for (int i = 0; i < 2 * m; ++ i) {
fa[i] = i;
}
int ans = 0;
while (n -- ) {
std::string s;
std::cin >> s;
for (int i = 0; i < s.size(); ++ i) {
int x = get(s[i]);
for (int j = 0; j < 4; ++ j) {
cur[i * 4 + 3 - j] = x >> j & 1;
}
}
int sum = 0;
for (int i = 0; i < m; ++ i) {
sum += cur[i];
}
for (int i = 1; i < m; ++ i) {
if (cur[i] && cur[i - 1]) {
sum -= merge(i, i - 1);
}
}
for (int i = 0; i < m; ++ i) {
if (cur[i] && last[i]) {
sum -= merge(i, i + m);
}
}
ans += sum;
for (int i = 0; i < m; ++ i) {
last[i] = cur[i];
}
for (int i = 0; i < m; ++ i) {
fa[i + m] = fa[i] + m;
fa[i] = i;
}
}
std::cout << ans << "\n";
}
F. Anti-Palindromize
题意:给你一个字符串,你要从重排它,使得\(i \in [1, n]\)都有\(s_i \neq s_{n-i+1}\)。设重排后的字符串为\(t\)最终价值为所有\(s_i = t_i\)的\(i\)的\(b_i\)的和。
考虑费用流。
对于每个字符,源点向他连出现次数的流量0的费用的边。那么这个字符在每个位置都可以放,但不能同时放在\(i\)和\(n-i+1\),那么我们可以只向前\(\frac{n}{2}\)个位置连边,同时这些位置往后面对应的位置连流量为1的边。这样一个字符在这两个位置上就只会放一个。同时讨论一下第\(i\)个位置放这个字符的费用。最后每个位置向汇点连流量为1费用为0的边。
点击查看代码
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;
std::cin >> n;
std::string S;
std::cin >> S;
std::vector<int> b(n);
for (int i = 0; i < n; ++ i) {
std::cin >> b[i];
}
const int N = n + 26 + 26 * n + 2;
MinCostFlow<int> f(N);
int s = N - 1, t = N - 2;
std::vector<int> cnt(26);
for (auto & c : S) {
++ cnt[c - 'a'];
}
for (int i = 0; i < 26; ++ i) {
f.addEdge(s, n + i, cnt[i], 0);
for (int j = 0; j < n / 2; ++ j) {
int x = i == S[j] - 'a' ? b[j] : 0, y = i == S[n - 1 - j] - 'a' ? b[n - 1 - j] : 0;
f.addEdge(n + i, n + 26 + i * n + j, 1, 0);
f.addEdge(n + 26 + i * n + j, n + 26 + i * n + n - 1 - j, 1, 0);
f.addEdge(n + 26 + i * n + j, j, 1, -x);
f.addEdge(n + 26 + i * n + n - 1 - j, n - 1 - j, 1, -y);
}
}
for (int i = 0; i < n; ++ i) {
f.addEdge(i, t, 1, 0);
}
auto [flow, cost] = f.flow(s, t);
std::cout << -cost << "\n";
}