VP Educational Codeforces Round 24
A. Diplomas and Certificates
点击查看代码
void solve() {
i64 n, k;
std::cin >> n >> k;
i64 cnt = (n / 2) / (k + 1) * k;
std::cout << cnt / k << " " << cnt << " " << n - cnt - cnt / k << "\n";
}
B. Permutation Game
题意:\(n\)个人围成一个圈,如果第\(i\)个人是领导者,那么下一个领导者是顺时针走\(a_i\)个位置。其中\(a\)是一个排列。选择给出\(m\)轮的领导者,求\(a\)。
可以通过\(l_{i+1} - l_i\)得出\(a_i\),检查有没有冲突和重复,没有出现过的随便填没用过的数。
点击查看代码
void solve() {
int n, m;
std::cin >> n >> m;
std::vector<int> l(m + 1);
for (int i = 1; i <= m; ++ i) {
std::cin >> l[i];
}
std::vector<int> a(n + 1);
std::set<int> s;
for (int i = 1; i <= n; ++ i) {
s.insert(i);
}
for (int i = 1; i + 1 <= m; ++ i) {
int t = (l[i + 1] - l[i] + n) % n;
if (t == 0) {
t = n;
}
if (a[l[i]] == 0) {
if (!s.count(t)) {
std::cout << -1 << "\n";
return;
}
s.erase(t);
a[l[i]] = t;
} else if (a[l[i]] != t) {
std::cout << -1 << "\n";
return;
}
}
for (int i = 1; i <= n; ++ i) {
if (a[i] == 0) {
a[i] = *s.begin();
s.erase(s.begin());
}
}
for (int i = 1; i <= n; ++ i) {
std::cout << a[i] << " \n"[i == n];
}
}
C. Sofa Thief
题意:\(n\)个沙发,每个沙发有两个坐标,对于\(i, j\),如果\(j\)有一个横坐标小于\(i\)的一个横坐标,则\(j\)在\(i\)的左边。如果\(j\)有一个横坐标大于\(i\)的一个横坐标,则\(j\)在\(i\)的右边。如果\(j\)有一个纵坐标小于\(i\)的一个纵坐标,则\(j\)在\(i\)的上边。如果\(j\)有一个纵坐标大于\(i\)的一个纵坐标,则\(j\)在\(i\)的下边。
求有没有一个沙发的左边有\(cnt_l\)个沙发,右边有\(cnt_r\)个沙发,上面有\(cnt_t\)个沙发,下面有\(cnt_d\)个沙发。
给每个坐标从小到大从大到小排序模拟,求出每个沙发各个方向有多少沙发。
点击查看代码
void solve() {
int k, n, m, L, R, T, D;
std::cin >> k >> n >> m;
std::vector<std::array<int, 3>> a;
for (int i = 0; i < k; ++ i) {
int x1, y1, x2, y2;
std::cin >> x1 >> y1 >> x2 >> y2;
a.push_back({x1, y1, i});
a.push_back({x2, y2, i});
}
std::cin >> L >> R >> T >> D;
std::vector<int> l(k, -1), r(k, -1), t(k, -1), d(k, -1);
std::sort(a.begin(), a.end());
std::set<int> s;
for (int i = 0; i < a.size(); ++ i) {
int j = i;
while (j + 1 < a.size() && a[j + 1][0] == a[i][0]) {
++ j;
}
for (int x = i; x <= j; ++ x) {
l[a[x][2]] = (int)s.size() - s.count(a[x][2]);
}
for (int x = i; x <= j; ++ x) {
s.insert(a[x][2]);
}
i = j;
}
s.clear();
std::sort(a.begin(), a.end(), std::greater<>());
for (int i = 0; i < a.size(); ++ i) {
int j = i;
while (j + 1 < a.size() && a[j + 1][0] == a[i][0]) {
++ j;
}
for (int x = i; x <= j; ++ x) {
r[a[x][2]] = (int)s.size() - s.count(a[x][2]);
}
for (int x = i; x <= j; ++ x) {
s.insert(a[x][2]);
}
i = j;
}
s.clear();
std::sort(a.begin(), a.end(), [&](std::array<int, 3> & a, std::array<int, 3> & b) {
return a[1] > b[1];
});
for (int i = 0; i < a.size(); ++ i) {
int j = i;
while (j + 1 < a.size() && a[j + 1][1] == a[i][1]) {
++ j;
}
for (int x = i; x <= j; ++ x) {
d[a[x][2]] = (int)s.size() - s.count(a[x][2]);
}
for (int x = i; x <= j; ++ x) {
s.insert(a[x][2]);
}
i = j;
}
s.clear();
std::sort(a.begin(), a.end(), [&](std::array<int, 3> & a, std::array<int, 3> & b) {
return a[1] < b[1];
});
for (int i = 0; i < a.size(); ++ i) {
int j = i;
while (j + 1 < a.size() && a[j + 1][1] == a[i][1]) {
++ j;
}
for (int x = i; x <= j; ++ x) {
t[a[x][2]] = (int)s.size() - s.count(a[x][2]);
}
for (int x = i; x <= j; ++ x) {
s.insert(a[x][2]);
}
i = j;
}
s.clear();
for (int i = 0; i < k; ++ i) {
// std::cout << l[i] << " " << r[i] << " " << t[i] << " " << d[i] << "\n";
if (l[i] == L && r[i] == R && t[i] == T && d[i] == D) {
std::cout << i + 1 << "\n";
return;
}
}
std::cout << -1 << "\n";
}
D. Multicolored Cars
题意:有一个数组,\(cntx_i\)为\(x\)这个数在\(1\)到\(i\)出现的次数。现在\(Alcie\)选了一个\(A\),你要选一个\(B\),使得对于任意一个\(i\)都有\(cntB_i \geq cntA_i\)。
用\(set\)存每个数的出现次数,然后从前往后更新\(cnt\),同时更新\(set\)。然后每次把小于\(cntA_i\)的位置都删掉。每次二分是\(log\)的,而每个数只会被删一次,总共是\(O(n)\)的,所以时间复杂度是\(O(nlogV)\),\(V\)是值域。
点击查看代码
void solve() {
int n, A;
std::cin >> n >> A;
std::vector<int> a(n);
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
}
std::set<std::pair<int, int>> s;
for (int i = 1; i <= 1000000; ++ i) {
s.insert({0, i});
}
std::vector<int> cnt(1000010);
for (auto & c : a) {
if (s.count({cnt[c], c})) {
s.erase({cnt[c], c});
++ cnt[c];
s.insert({cnt[c], c});
}
auto it = s.lower_bound({cnt[A], 0});
s.erase(s.begin(), it);
}
s.erase({cnt[A], A});
if (s.empty()) {
std::cout << -1 << "\n";
} else {
std::cout << s.begin()->second << "\n";
}
}
E. Card Game Again
题意:求有多少区间的乘积和是\(k\)的倍数。
对\(k\)质因数分解,得到\(m\)个质数及其个数,然后记\(sum[i][j]\)为\([1, i]\)里第\(j\)个质数出现的次数。那么我们枚举右端点,二分合法的最右边的左端点。
点击查看代码
void solve() {
int n, k;
std::cin >> n >> k;
std::vector<int> a(n + 1);
for (int i = 1; i <= n; ++ i) {
std::cin >> a[i];
}
std::map<int, int> mp;
for (int i = 2; i <= k / i; ++ i) {
if (k % i == 0) {
while (k % i == 0) {
++ mp[i];
k /= i;
}
}
}
if (k > 1) {
++ mp[k];
}
std::vector<int> sumk;
std::vector<int> b;
for (auto & [x, cnt] : mp) {
b.push_back(x);
sumk.push_back(cnt);
}
int m = (int)b.size();
std::vector sum(n + 1, std::vector<int>(m));
for (int i = 1; i <= n; ++ i) {
sum[i] = sum[i - 1];
for (int j = 0; j < m; ++ j) {
int cnt = 0;
while (a[i] % b[j] == 0) {
++ cnt;
a[i] /= b[j];
}
sum[i][j] += cnt;
}
}
auto check = [&](int l, int r) -> bool {
for (int i = 0; i < m; ++ i) {
if (sum[r][i] - sum[l - 1][i] < sumk[i]) {
return false;
}
}
return true;
};
i64 ans = 0;
for (int i = 1; i <= n; ++ i) {
int l = 1, r = i;
while (l < r) {
int mid = l + r + 1 >> 1;
if (check(mid, i)) {
l = mid;
} else {
r = mid - 1;
}
}
if (!check(l, i)) {
l = 0;
}
ans += l;
}
std::cout << ans << "\n";
}
F. Level Generation
题意:给你\(n\)个点,你要构造一个边数为\(m\)的图,使得图里的桥的数量\(\geq \lceil \frac{m}{2} \rceil\)。求\(m\)最大多少。
我们肯定是先用\(\lceil \frac{m}{2} \rceil\)条边构造一棵树,然后其它点选树上一个点构成一个完全图。那么可以二分边数。
点击查看代码
void solve() {
i64 n;
std::cin >> n;
auto check = [&](i64 m) -> bool {
i64 cnt = (m + 1) / 2;
return (n - cnt) * (n - cnt - 1) / 2 >= m - cnt;
};
if (n == 1) {
std::cout << 0 << "\n";
return;
}
i64 l = 1, r = 2 * n;
while (l < r) {
i64 mid = l + r + 1 >> 1ll;
if (check(mid)) {
l = mid;
} else {
r = mid - 1;
}
}
std::cout << l << "\n";
}
G. Four Melodies
题意:在一个数组种选四个不相交的子序列,每个子序列相邻的两个数要么相差1,要么余7相同。使得四个子序列长度加起来最大。
本质是在一个图里选\(k\)条路径使得路径和最大的问题。
如果我们向每个\(|a_i - a_j| = 1\)或\(a_i \equiv a_j \pmod{7}\)的\((i, j)\)由\(i\)向\(j\)连边。那么就等价于求图里的两段路径,使得路径长度之和最大,这是个经典费用流问题。我们把每个点拆成出度和入度,一个点的入度向出度连费用为1流量为1的边,可以连边的由出度向入度连费用为0流量为1的边,同时源点给每个点的入度连费用为0流量为1的边,每个点的出度向汇点连费用为0流量为1的边,然后为了满足两条路径的需求,超级源点向源点连费用为0流量为2的边,汇点向超级汇点连费用为0流量为4的边。但这样边数太大,时间复杂度不能接受。
我们可以对于每个\(i\)的出度,往右边最近的\(a_i - a_j = 1\)以及\(a_i - a_j = -1\)的\(j\)的入度连边,因为满足条件的\(a_j\)值只有两种,我们选最近的可以给后面留出更大空间,显然更优。然后对于\(a_i \equiv a_j \pmod{7}\)的最近的\(j\),我们由\(i\)的出度向\(j\)的入度连边,同时\(i\)的入度向\(j\)的入度连边,这意味着选了\(i\)可以继续选\(j\),或者如果不选\(i\)下一个就可以选\(j\)。这样就解决了。
这个问题可以由\(4\)条路径扩展成\(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;
std::cin >> n;
std::vector<int> a(n);
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
}
MinCostFlow<int> mf(2 * n + 4);
int s = 2 * n, t = 2 * n + 1, S = 2 * n + 2, T = 2 * n + 3;
mf.addEdge(S, s, 4, 0);
mf.addEdge(t, T, 4, 0);
for (int i = 0; i < n; ++ i) {
mf.addEdge(s, i, 1, 0);
mf.addEdge(i + n, t, 1, 0);
mf.addEdge(i, i + n, 1, -1);
for (int j = i + 1; j < n; ++ j) {
if (a[i] == a[j] + 1) {
mf.addEdge(i + n, j, 1, 0);
break;
}
}
for (int j = i + 1; j < n; ++ j) {
if (a[i] == a[j] - 1) {
mf.addEdge(i + n, j, 1, 0);
break;
}
}
for (int j = i + 1; j < n; ++ j) {
if (a[i] % 7 == a[j] % 7) {
mf.addEdge(i + n, j, 1, 0);
mf.addEdge(i, j, 1, 0);
break;
}
}
}
std::cout << -mf.flow(S, T).second << "\n";
}