Codeforces Round 1054 (Div. 3)
A. Be Positive
模拟。
只需要把负数和零变为正即可。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
void solve() {
int n;
cin >> n;
int cnt[3] {};
for (int i = 1; i <= n; i += 1) {
int x;
cin >> x;
if (x == -1) {
cnt[2] += 1;
}
else {
cnt[x] += 1;
}
}
cout << (cnt[0] + (cnt[2] % 2) * 2) << "\n";
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
B. Unconventional Pairs
排序。
排完序后两两的差值就是最小的,两个两个配对取最大即可。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
void solve() {
int n;
cin >> n;
vector<int> a(n + 1);
for (int i = 1; i <= n; i += 1) {
cin >> a[i];
}
sort(a.begin() + 1, a.end());
int ans = 0;
for (int i = 1; i <= n; i += 2) {
ans = max(ans, a[i + 1] - a[i]);
}
cout << ans << "\n";
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
C. MEX rose
模拟。
要使得 \(\text{Mex}(a) = x\),那么 \(a\) 中等于 \(x\) 的都不能要,其次 \(0\sim x - 1\) 可能会有些数没有,可以把 \(x\) 改成这些缺的数,所以最后就是等于 \(x\) 的和缺的数取个最大值即可。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
void solve() {
int n, k;
cin >> n >> k;
vector<int> cnt(n + 2);
for(int i = 1;i <= n;i += 1){
int x;
cin >> x;
cnt[x] += 1;
}
int ans = cnt[k], res = 0;
for(int i = 0;i < k;i += 1){
res += cnt[i] == 0;
}
ans = max(ans, res);
cout << ans << "\n";
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
D - A and B
贪心。
vp 时想到了,但没写,不知道想啥去了枚举两个字母,取中间位置时,移动的次数最小,计算一下取个最小就行。
放个官方[1]证明吧:
固定一个字母 \(c\in{a,b}\) ,让它的位置为 \(p_0< p_1<\dots<p_{m-1}\) 。如果我们将这些 \(m\) 副本放入一个从索引 \(L\) 开始的连续块中,则需要的相邻交换次数为
$ \sum_{i=0}^{m-1} | p_i - (L+i) | = \sum_{i=0}^{m-1} | (p_i - i) - L|$,让 \(b_i=p_i-i\),当 \(L\) 是 \({b_i}\) 的中值时,函数 \(\sum_i |b_i-L|\) 被最小化。因此,将所有 \(c\) 聚类的最优代价为 \(\text{cost}(c) = \sum_{i=0}^{m-1} | b_i - \text{med}(b) |\),\(\text{where } b_i = p_i - i.\)
答案是 \(\min(\operatorname{cost}(a),\operatorname{cost}(b))\) 。
这计算了相邻交换的最小数量,因为每次交换将两种类型之间的成对反转的总和正好减少了 \(1\) ,并且对齐到一个连续的块达到了最小值。该算法计算位置,构建 \(b_i\) ,取中位数,并对绝对偏差求和——所有这些都在已知位置后的线性时间内完成。
时间复杂度为 \(O(n)\) 。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
void solve() {
int n;
cin >> n;
string s;
cin >> s;
s = " " + s;
i64 ans = 1E12;
for(auto c : {'a', 'b'}){
vector<int> p;
for(int i = 1;i <= n;i += 1){
if(s[i] == c){
p.push_back(i);
}
}
if(p.empty()){
continue;
}
int k = p[p.size() / 2];
i64 res = 0;
for(int i = int(p.size()) / 2,j = 0;i >= 0;i -= 1, j += 1){
res += k - j - p[i];
}
for(int i = int(p.size()) / 2,j = 0;i < p.size();i += 1, j += 1){
res += p[i] - (k + j);
}
ans = min(ans, res);
}
cout << ans << "\n";
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
E. Hidden Knowledge of the Ancients
双指针。
枚举左端点,维护两个指针,一个为当前从 \(i\) 开始能满足出现 \(k\) 个颜色的最远位置,一个为刚好满足 \(k\) 个颜色的位置,然后两者和给定的区间长度判一下,就是当前 \(i\) 的符合的右端点的范围。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
void solve() {
int n, k, l, r;
cin >> n >> k >> l >> r;
vector<int> a(n + 1);
for (int i = 1; i <= n; i += 1) {
cin >> a[i];
}
i64 ans = 0;
map<int,int> mp;
int l1 = 0, r1 = 0, cnt = 0;
for (int i = 1; i <= n; i += 1) {
while (l1 + 1 <= n && cnt < k) {
l1 += 1;
if (!mp.count(a[l1])) {
cnt += 1;
}
mp[a[l1]] += 1;
}
r1 = max(r1, l1);
while (r1 + 1 <= n && mp.count(a[r1 + 1])) {
r1 += 1;
}
if (l1 - i + 1 > r || cnt != k || r1 - i + 1 < l) {
mp[a[i]] -= 1;
if (mp[a[i]] == 0) {
cnt -= 1;
mp.erase(a[i]);
}
continue;
}
int d1 = min(r1, i + r - 1);
int d2 = max(l1, i + l - 1);
ans += d1 - d2 + 1;
mp[a[i]] -= 1;
if (mp[a[i]] == 0) {
cnt -= 1;
mp.erase(a[i]);
}
}
cout << ans << "\n";
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
F. Nezuko in the Clearing
二分。
二分休息次数,假设休息次数为 \(x\),那么当前就走了 \(x+1\) 段,每段都会走 \(s=\lfloor\frac{d}{x+1}\rfloor\),那么每段 \(1\sim s\) 的花费为 \(\frac{s(1+s)}{2}\),剩余有 \(d\%(x + 1)\) 段会走 \(s+1\) ,均摊到每一段上,所以总花费为 \(\frac{s(1+s)}{2}\times(x+1)+(1+s)\times (d\% (x+1))\),那么总血量为 \(h + x\),两者比较一下即可。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
void solve() {
int h, d;
cin >> h >> d;
int lo = 0, hi = d, ans = 0;
while (lo <= hi) {
int mid = lo + hi >> 1;
int nd = d / (mid + 1);
i64 need = i64(1 + nd) * nd / 2 * (mid + 1);
need += i64(nd + 1) * (d % (mid + 1));
if (need < h + mid) {
hi = mid - 1;
ans = mid;
}
else {
lo = mid + 1;
}
}
cout << ans + d << "\n";
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
G. Buratsuta 3
解法一:根号分治。
根据 \(a_i\) 的出现次数,设置一个限制 \(B\),然后根据每次查询 \([l,r]\),定义 \(g = \lfloor\frac{r-l+1}{3}\rfloor\),考虑 \(g\ge B\) 和 \(g <B\) 的分别处理:当 \(g \ge B\) 时,答案只能来自于出现次数不少于 \(B\) 次的数字,这样的数不会超过 \(\frac n B\) 个,所以我们可以对这 \(\frac nB\) 个数预处理出前缀和,查询时直接判断即可;当 \(g <B\) 时, 直接区间暴力查找,这部分复杂度为 \(O(B)\)。
总复杂度为 \(O(\frac{n^2}{B}+q(B+\frac nB))\),显然当 \(B=\sqrt n\),得到最优复杂度 \(O((n+q)\sqrt n)\),但是对于 \(n\sqrt n\) 的预处理 \(\text{256MB}\) 太容易 \(\text{MLE}\) 了,需要微调一下,我这里是取 \(B = n^{\frac 35}\),最后跑了 \(\text{1.6s}\),内存 \(\text{2300KB}\)。
感觉数据只卡了根号的,空间差距有点太大
参考[2]。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
void solve() {
int n, q;
cin >> n >> q;
vector<int> a(n + 1), val;
for(int i = 1; i <= n; i += 1) {
cin >> a[i];
val.push_back(a[i]);
}
sort(val.begin(), val.end());
val.erase(unique(val.begin(), val.end()), val.end());
for(int i = 1; i <= n; i += 1) {
a[i] = lower_bound(val.begin(), val.end(), a[i]) - val.begin();
}
int B = pow(n, 3.0 / 5), m = val.size();
vector<int> cnt(m), has;
for(int i = 1; i <= n; i += 1) {
cnt[a[i]] += 1;
}
for(int i = 0; i < m; i += 1) {
if(cnt[i] >= B) {
has.push_back(i);
}
}
int k = has.size();
vector<vector<int>> pre(k);
for(int i = 0; i < k; i += 1) {
int x = has[i];
pre[i] = vector<int>(n + 1);
for(int j = 1; j <= n; j += 1) {
pre[i][j] += pre[i][j - 1] + (x == a[j]);
}
}
vector<int>(m).swap(cnt);
while(q --) {
int l, r;
cin >> l >> r;
int b = (r - l + 1) / 3 + 1;
vector<int> ans;
if(b >= B) {
for(int i = 0; i < k; i += 1) {
if(pre[i][r] - pre[i][l - 1] >= b) {
ans.push_back(has[i]);
}
}
} else {
for(int i = l; i <= r; i += 1) {
cnt[a[i]] += 1;
if(cnt[a[i]] >= b && find(ans.begin(), ans.end(), a[i]) == ans.end()) {
ans.push_back(a[i]);
}
}
for(int i = l; i <= r; i += 1) {
cnt[a[i]] -= 1;
}
}
sort(ans.begin(), ans.end());
if(ans.empty()) {
cout << "-1\n";
continue;
}
for(auto &x : ans) {
cout << val[x] << " ";
}
cout << "\n";
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
解法二:随机化。
对于查询区间来说 \([l,r]\) 来说,定义 \(g = \lfloor\frac{r-l+1}{3}\rfloor\),那么当一个数的出现次数超过 \(g\) 次,那么它肯定占据了超过三分之一的段,因此在一次随机抽取中选到它的概率为 \(p > \frac 13\),在 \(k\) 次独立抽取之后,错过的概率为 \((\frac 23)^k\),当 \(k=50\) 时,这个概率可以忽略不计。
检查一个数是否满足要求,先对序列离散化以后,将每个数的位置记录一下,然后查询的时候二分 \(r\) 和 \(l-1\) 减一下即可。
参考[1:1]。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());
void solve() {
int n, q;
cin >> n >> q;
vector<int> a(n + 1), val;
for(int i = 1; i <= n; i += 1) {
cin >> a[i];
val.push_back(a[i]);
}
sort(val.begin(), val.end());
val.erase(unique(val.begin(), val.end()), val.end());
int m = val.size();
vector<vector<int>> has(m);
for(int i = 1; i <= n; i += 1) {
a[i] = lower_bound(val.begin(), val.end(), a[i]) - val.begin();
has[a[i]].push_back(i);
}
auto get = [&](int x,int l)->int{
return prev(upper_bound(has[x].begin(), has[x].end(), l)) - has[x].begin();
};
while(q--) {
int l, r;
cin >> l >> r;
int b = (r - l + 1) / 3 + 1;
vector<int> ans;
for(int _ = 0; _ < 50; _ += 1) {
int x = rng() % (r - l + 1) + l;
if(get(a[x], r) - get(a[x], l - 1) >= b) {
if(find(ans.begin(), ans.end(), a[x]) == ans.end()) {
ans.push_back(a[x]);
}
}
}
if(ans.empty()) {
cout << "-1\n";
continue;
}
sort(ans.begin(), ans.end());
for(auto &x : ans) {
cout << val[x] << " ";
}
cout << "\n";
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
解法三:数据结构。
通过分析可知,超过区间三分之一的数最多只会存在两个。
考虑用线段树维护,每个节点存储最多两个带计数器的候选项,向上合并的时候需要用 \(\text{Misra-Gries}\) 算法对频繁项处理,如果两子树的项相同,就累加计数;否则就减掉最小值,谁计数器为 \(0\) 就踢掉谁。
最后处理出区间的频繁项,然后像上面随机化那样检查即可。
\(\text{Misra-Gries}\) 算法步骤:
首先建立一个大小为 \(k\) 的数组 \(T\)。
对于数据流中依次到达的项 \(i\),进行如下处理:如果项 \(i\) 在数组 \(T\) 中,则其对应的计数器 \(c_i\)++;如果项 \(i\) 不在数组中,且数组 \(T\) 中的元素个数小于 \(k - 1\),则将项 \(i\) 加入数组 \(T\),并为其分配计数器 \(c_i = 1\);其他情况,将数组 \(T\) 中所有元素的计数器减 \(1\),此时如果数组 \(T\) 中存在元素的计数器值为 \(0\),则从数组 \(T\) 移除这个元素。
当完成对数据流的扫描后,数组 \(T\) 中保存的 \(m\ (m < k - 1)\) 个元素即是数据流中的频繁项。
参考[3]。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
template<class Info>
struct SegmentTree {
int n;
vector<Info> info;
SegmentTree(): n(0) {}
SegmentTree(int n_, Info v_ = Info()) {
init(n_, v_);
}
template<class T>
SegmentTree(vector<T> init_) {
init(init_);
}
void init(int n_, Info v_ = Info()) {
init(vector(n_, v_));
}
template<class T>
void init(vector<T> init_) {
n = init_.size();
info.assign((4 << __lg(n) + 1) + 10, Info());
function<void(int, int, int)> build = [&](int u, int l, int r) {
if (l == r) {
info[u] = init_[l - 1];
return ;
}
i64 m = (l + r) / 2;
build(2 * u, l, m);
build(2 * u + 1, m + 1, r);
pushup(u);
};
build(1, 1, n);
}
void pushup(int u) {
info[u] = info[2 * u] + info[2 * u + 1];
}
void modify(int u, int l, int r, int x, const Info &v) {
if (l == r) {
info[u] = v;
return;
}
int m = (l + r) / 2;
if (x <= m) {
modify(2 * u, l, m, x, v);
} else {
modify(2 * u + 1, m + 1, r, x, v);
}
pushup(u);
}
void modify(int u, const Info &v) {
modify(1, 1, n, u, v);
}
Info rangeQuery(int u, int l, int r, int x, int y) {
if (l > y || r < x) {
return Info();
}
if (l >= x && r <= y) {
return info[u];
}
int m = (l + r) / 2;
return rangeQuery(2 * u, l, m, x, y) + rangeQuery(2 * u + 1, m + 1, r, x, y);
}
Info rangeQuery(int l, int r) {
return rangeQuery(1, 1, n, l, r);
}
};
struct Info {
int val[2] {};
int cnt[2] {};
};
Info operator+(const Info &l, const Info &r) {
Info res = l;
for(int i = 0; i < 2; i += 1) {
int val = r.val[i], cnt = r.cnt[i];
if(res.cnt[0] && res.val[0] == val) {
res.cnt[0] += cnt;
continue;
}
if(res.cnt[1] && res.val[1] == val) {
res.cnt[1] += cnt;
continue;
}
int t = min({res.cnt[0], res.cnt[1], cnt});
res.cnt[0] -= t;
res.cnt[1] -= t;
cnt -= t;
if(cnt) {
if(!res.cnt[0]) {
res.val[0] = val;
res.cnt[0] = cnt;
} else {
res.val[1] = val;
res.cnt[1] = cnt;
}
}
}
return res;
}
void solve() {
int n, q;
cin >> n >> q;
vector<int> a(n), val;
for(int i = 0; i < n; i += 1) {
cin >> a[i];
val.push_back(a[i]);
}
sort(val.begin(), val.end());
val.erase(unique(val.begin(), val.end()), val.end());
int m = val.size();
SegmentTree<Info> seg(n);
vector<vector<int>> has(m);
for(int i = 0; i < n; i += 1) {
a[i] = lower_bound(val.begin(), val.end(), a[i]) - val.begin();
has[a[i]].push_back(i + 1);
Info info;
info.val[0] = a[i], info.cnt[0] = 1;
seg.modify(i + 1, info);
}
auto get = [&](int x,int l)->int{
return prev(upper_bound(has[x].begin(), has[x].end(), l)) - has[x].begin();
};
while(q--) {
int l, r;
cin >> l >> r;
int b = (r - l + 1) / 3 + 1;
vector<int> ans;
auto res = seg.rangeQuery(l, r);
for(int i = 0; i < 2; i += 1) {
int v = res.val[i];
int c = res.cnt[i];
if(c) {
if(get(v, r) - get(v, l - 1) >= b) {
if(find(ans.begin(), ans.end(), val[v]) == ans.end()) {
ans.push_back(val[v]);
}
}
}
}
if(ans.empty()) {
cout << "-1\n";
continue;
}
sort(ans.begin(), ans.end());
for(auto &x : ans) {
cout << x << " ";
}
cout << "\n";
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}

浙公网安备 33010602011771号