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;
}

  1. Codeforces Round 1054 (Div. 3) Editorial ↩︎ ↩︎

  2. https://codeforces.com/blog/entry/146793?#comment-1312926 ↩︎

  3. https://www.cnblogs.com/super-zhang-828/p/7353217.html ↩︎

posted @ 2025-09-27 19:55  Ke_scholar  阅读(48)  评论(0)    收藏  举报