CF 有趣题目

SuperSupper·2024-09-01 21:56·10 次阅读

CF 有趣题目

CF1157F Maximum_Balanced_Circle#

Problem

题意:

给出一个长度为 \(n\) 的序列 \(a\),你可以选出序列的任意子集。记这个子集为 \(b\),大小为 \(k\),则需要满足 \(\lvert b_i-b_{(i+1)\bmod k}\rvert \le 1\)。你需要最大化 \(k\) 的值,并输出选出的子集 \(b\)

Solution 注意到最终的集合肯定是形如 $l, l, ···, l + 1, l + 1, ···, r, r, ···, l + 1, l + 1, ···(l, l, ···)$,所以最小值和最大值出现次数一定大于等于 1,中间的数出现次数一定大于等于 2,然后模拟一遍算就行了
Code
Copy
#include <iostream> #include <numeric> using namespace std; using ll = long long; using pii = pair<int, int>; const int kN = 2e5 + 1; int n, c[kN], p[kN], l[kN]; pii ans; int main() { cin.tie(0)->sync_with_stdio(0); cin >> n; for (int i = 1, x; i <= n; i++) { cin >> x; c[x]++; } fill(l, l + kN, 1e9); for (int i = 1; i < kN; i++) { p[i] = p[i - 1] + c[i]; if (c[i]) { if (l[i - 1] == 1e9) { l[i] = i; } else { l[i] = l[i - 1]; } if (!ans.first || p[i] - p[l[i] - 1] > p[ans.second] - p[ans.first - 1]) { ans = {l[i], i}; } if (c[i] == 1) { l[i] = i; } } } cout << accumulate(c + ans.first, c + ans.second + 1, 0) << '\n'; for (int i = ans.first; i <= ans.second; i++) { while (c[i] >= 2) { cout << i << ' ', c[i]--; } } for (int i = ans.second; i >= ans.first; i--) { while (c[i] >= 1) { cout << i << ' ', c[i]--; } } return 0; }

CF850B Arpa and a list of numbers#

Problem

题意:

我们认为一个序列是bad序列,当且仅当该序列非空且gcd是1。现在有两种操作:

1、删除一个数,代价为x。

2、给一个数+1,代价为y。

现给出该序列和x,y,求将给定序列变为good序列(非bad)的最小代价。

Solution

发现对于每个数一定有一个上限使得再加 1 不如直接删掉更优,于是在值域上把这一段区间先处理一下。考虑枚举每一个 \(d\),然后检查每一个 \(d\) 的倍数是落在 +1 的区间还是删掉的区间,但是要注意同一个区间里面不能检查多次,可以将每一个点的所有包含其的区间的右端点最大值的值记录下来,于是可以用线段树维护这个东西,对于重叠的区间直接将 \(x\) 加上就行了。但是枚举每一个 \(d\) 的时间复杂度是 \(O(V log V)\) 的,有点慢,考虑有没有一种更高效的做法。由简单的数学知识可知,若对于一个 \(p\)\(p | d\),则 \(p | a_i\),所以直接枚举所有的质数就行,时间复杂度大概应该也许就能过了

Code

摆了,不想写:)

CF863F Almost Permutation#

Problem

题意:

现有一个长度为 \(n\) 的未知数组
\(A\) , 每个元素都是 \([1,n]\) 内的整数。

有如下两种共 \(q\) 个限制 :

  1. \([l,r]\) 中所有数都大于等于 \(v\)

  2. \([l,r]\) 中所有数都小于等于 \(v\)

\(cnt(i)\)\(i\)\(A\) 中的出现次数。

求出在所有满足条件的数组中,下列式子的最小值 :

\[\sum\limits_{i=1}^ncnt(i)^2 \]

若不存在满足条件的数组,输出 \(-1\)

数据范围:\(1 \le n \le 50, 1\le q \le 100\)

Solution

网络流题总是这么难看出来首先可以将题目转换为对于每一个数有一个取值范围 \([l_i, r_i]\),求最小的所有数字的出现次数的平方和。发现不好 dp,可以将每个点选的数字的代价看作费用,跑费用流。怎么建图?先将每个点连一个源点,流量为 1(一下所有边默认为 1),费用为 0,表示这个点可以选择一次;再将每个点向可以选择的数字连边,费用为 0,表示可以选择的数字;如何体现选择次数和代价的关系?可以差分建边,即建 \(n\) 个边,费用为 \(i^2 - (i - 1)^2\),显然选边一定从代价小的开始选,可以保证正确性;最后还需要控制每个点只能选择一次,所以将数字的出点向元素的出点建费用为 0 的边,再将出边连向汇点即可

Code
Copy
#include <iostream> #include <queue> using namespace std; const int kN = 1e4 + 1, kM = 1e4 + 1; int n, m, s, t, q, k = 1, ans, _ans, l[kN], r[kN], vis[kN], res[kN]; struct AC { int f, b, fe; } v[kN]; struct AK { int y, c, w, n; } e[kM << 1]; void A(int x, int y, int c, int w) { auto A = [&](int i, int x, int y, int c, int w) { e[i] = {y, c, w, v[x].fe}, v[x].fe = i; }; A(++k, x, y, c, w), A(++k, y, x, 0, -w); } bool B() { for (int i = 0; i < kN; i++) { v[i].f = i == s, res[i] = 1e9; } res[s] = 0; queue<int> q; for (q.push(s); q.size(); q.pop()) { vis[q.front()] = 0; for (int i = v[q.front()].fe; i; i = e[i].n) { if (e[i].c && res[q.front()] + e[i].w < res[e[i].y]) { res[e[i].y] = res[q.front()] + e[i].w; v[e[i].y].f = i, v[e[i].y].b = min(v[q.front()].b, e[i].c); if (!vis[e[i].y]) { vis[e[i].y] = 1; q.push(e[i].y); } } } } return res[t] != 1e9; } int main() { cin.tie(0)->sync_with_stdio(0); cin >> n >> m, s = kN - 2, t = kN - 1; fill(l, l + kN, 1), fill(r, r + kN, n); for (int i = 1, o, l, r, x; i <= m; i++) { cin >> o >> l >> r >> x; for (int j = l; j <= r; j++) { if (o == 1) { ::l[j] = max(::l[j], x); } else { ::r[j] = min(::r[j], x); } } } // 1 ~ n 每个元素的入点;n + 1 ~ 2 * n 每个元素的出点;2 * n + 1 ~ 3 * n 每个数字的入点;3 * n + 1 ~ 4 + n 每个数字的出点 for (int i = 1; i <= n; i++) { A(s, i, 1, 0), A(i + n, t, 1, 0); for (int j = 1; j <= n; j++) { A(i + 2 * n, i + 3 * n, 1, 2 * j - 1); } for (int j = l[i]; j <= r[i]; j++) { A(i, j + 2 * n, 1, 0), A(j + 3 * n, i + n, 1, 0); } } v[s].b = 1e9; while (B()) { ans += v[t].b; _ans += v[t].b * res[t]; for (int i = t; i != s; i = e[v[i].f ^ 1].y) { e[v[i].f].c -= v[t].b, e[v[i].f ^ 1].c += v[t].b; } } if (ans < n) { cout << "-1"; } else { cout << _ans; } return 0; }

CF12D Ball#

Problem

题意:

\(N\) 个女士去参加舞会。每个女士有三个值 \(a_i,b_i,c_i\)。如果一位女士发现有其它女士的这三个值都比自己高的话就会去跳楼.求有多少跳楼的女士。

读入:第一行为 \(N\),第二行为每个人的值,第三行为每个人的值,第四行为每个人的值。 输出:一个整数,代表问题的答案。

数据范围:\(1 \leqslant N \leqslant 500000\)\(0 \leqslant a_i,b_i,c_i \leqslant 10^9\)

Solution

显然是让我们求有多少个点被偏序了,可以三位偏序,但是有更简单的 \(n \log n\) 做法。首先按 \(a_i\) 从大到小排序,这样先枚举到的一定比后枚举到的 \(a_i\) 大。这时候就可以将题目转化成有多少个人 \(b_j > b_i\)\(c_j > c_i\) 了,然后将每个 \(c_i\) 放到对应的 \(b_i\) 上(如有相同,取最大值)求个区间最大值即可。有一些小细节需要注意,当 \(a_i\) 相同时,有可能会被统计到答案,但是这是不合法的,可以对 \(a_i\) 分组,或者在排序的时候将 \(b_i\)\(c_i\) 做为第二、三关键字排序,但是是从大到小的,可以避免被统计到答案。

时间复杂度 \(O(n \log n)\),但是我的代码是 \(O(n \log^2 n)\) 的,瓶颈在排序,将 \(b_i\) \(O(n)\) 离散化或哈希就可以变成 \(O(n \log n)\)

Code
Copy
#include <algorithm> #include <iostream> #include <map> using namespace std; using ll = long long; using pll = pair<ll, ll>; using pii = pair<int, int>; const int kN = 5e5 + 1; int n, k, ans, l[kN], a[kN], b[kN], c[kN], t[kN << 2]; map<int, int> m; void U(int x, int l, int r, int k, int c) { if (l == r) { t[x] = max(t[x], c); return; } int m = l + r >> 1; if (k <= m) { U(x * 2, l, m, k, c); } if (k > m) { U(x * 2 + 1, m + 1, r, k, c); } t[x] = max(t[x * 2], t[x * 2 + 1]); } int Q(int x, int l, int r, int nl, int nr) { if (nl <= l && r <= nr) { return t[x]; } int m = l + r >> 1, res = 0; if (nl <= m) { res = Q(x * 2, l, m, nl, nr); } if (nr > m) { res = max(res, Q(x * 2 + 1, m + 1, r, nl, nr)); } return res; } int main() { cin.tie(0)->sync_with_stdio(0); cin >> n; for (int i = 1; i <= n; i++) { cin >> a[i], l[i] = i; } for (int i = 1; i <= n; i++) { cin >> b[i]; } sort(l + 1, l + n + 1, [](int x, int y) { return b[x] < b[y]; }); for (int i = 1; i <= n; i++) { cin >> c[i]; if (m.find(b[l[i]]) == m.end()) { m[b[l[i]]] = ++k; } } auto C = [&](int x, int y) { if (a[x] != a[y]) { return a[x] > a[y]; } else if (b[x] != b[y]) { return m[b[x]] < m[b[y]]; } else { return c[x] < c[y]; } }; sort(l + 1, l + n + 1, C); for (int i = 1; i <= n; i++) { ans += Q(1, 1, k, m[b[l[i]]] + 1, k) > c[l[i]], U(1, 1, k, m[b[l[i]]], c[l[i]]); } cout << ans; return 0; }
posted @   sb-yyds  阅读(10)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示
目录