CF Round 1040 Div 2

A

题意

给你一个集合 \(S\),你有两种操作:

你可以选择一个 \(S\) 的子集 \(T\) 然后

  • 加上 \(T\) 中所有元素和。
  • 加上 \(\text{mex}(T)\)

然后把 \(T\)\(S\) 中删掉。

Solution

考虑 \(\text{sum}\) 的值不变,然后如果你选了一个 \(\text{mex}(T) = x\) 的,那当 \(x > 1\) 时,加上 \(T\) 肯定不差。

所以就是把非零元素全加上,\(0\) 的就 \(\text{mex}\) 一下。

Code

cin >> n;
int ans = 0;
for (int i = 1; i <= n; i++) {
    int x; cin >> x;
    ans += (x == 0 ? 1 : x);
}
cout << ans << "\n";

B

题意

给你 \(n\) 个数 \(a_i\)\(a_i = 0/1/2\),且每个数至少出现一次。

你要从 \(1\) 走到 \(n\),每一步可以选择往左或往右走。

可以走无限步,每走到一个格子上就获取他的 \(a_i\) 一次。

求是否有一种方案可以使得获得价值为恰好为 \(s\)

你可以随意重排这个序列来避免达到这个要求。如果可以避免那输出方案。否则输出 \(-1\)

Solution

考虑如果 \(sum = s\) 那么肯定是 \(-1\)

如果 \(sum > s\) 就是连每个数只拿一次都超了的话那随便怎么排都可以避免。

否则如果 \(s = sum + 1\),那么如果有相邻的 \(01\) 就不行了,所以我们要避免这个情况。

然后还有 \(s > sum + 1\) 那么如果有相邻的 \(01,02,12\) 就都不行了,因为避免其中一个那剩下两个一定出现,他们可以凑出无穷种情况。所以是 \(-1\)

Code

cin >> n >> s; int sum = 0;
int b[3] = { 0 };
for (int i = 1; i <= n; i++) cin >> a[i], sum += a[i], b[a[i]]++;
if (sum == s || s > sum + 1) {
    cout << "-1\n";
} else {
    for (int i = 1; i <= b[0]; i++) cout << 0 << " ";
    for (int i = 1; i <= b[2]; i++) cout << 2 << " ";
    for (int i = 1; i <= b[1]; i++) cout << 1 << " ";
    cout << "\n";
}

C

题意

给你一个集合 \(S\),里面包含一些数对 \((u, v)\)

你有两个函数,\(f(T)\) 表示把 \(T\) 这个集合里的数对看成数轴上的线段,他们的并集的长度。

\(g(T)\) 表示把 \(T\) 这个集合里的数对看成 \(u\to v\) 的边,这张图上的环的长度。\(x_1 \to x_2 \to \cdots \to x_m \to x_1\),则函数值为 \(m\)

你要在 \(S\) 里选一个子集,使得 \(f(S') - g(S')\) 最大。输出你选的数对的下表。

\(n \leq 3\times 10^3\)

Solution

hint > 考虑这个 $g$ 值,我们肯定希望加入了一个数对,尽管 $g$ 加大了,但是 $f$ 加大的更多,但是会有这种情况么?

因为你是一个环,假设你环是这样的:\(1\to 2 \to 3 \to 4 \to 1\)。那你完全可以把这个 \((4, 1)\) 删了,然后 \(f\) 还不变。

所以最优情况就是不选成环。那我们直接用并查集维护连通性就行了,能选就选。

Code

cin >> n;
for (int i = 1; i <= 2 * n; i++) p[i] = i;
for (int i = 1; i <= n; i++) cin >> l[i] >> r[i];
vector<int> ans;
for (int i = 1; i <= n; i++) {
    int x = fifa(l[i]), y = fifa(r[i]);
    if (x != y) p[x] = y, ans.push_back(i);
}
cout << ans.size() << "\n";
for (auto u : ans) cout << u << " ";
cout << "\n";

D

题意

给你一个 \(1\sim n\) 的排列 \(p\)

你可以对于每个 \(i\) 选择 \(p_i = p_i \ /\ 2n - p_i\)

\(p\) 的最小逆序对数量。

Solution

hint > 考虑如果 $p_i = 1 \ / \ 2n - 1$ 那么他的逆序对数量如何算
hint2 > 推广这一结论

考虑对于 \(p_i = 1 \ / \ 2n - 1\),前者的贡献是前面比他大的数的数量,后者是后面比他大的数量(这个是对于原 \(p_i\) 来说的)。

那么把 \(1\) 删掉之后,这就是原来的一个子问题,可以一摸一样去处理。

你可以用一个数据结构直接暴力维护那个东西。当然直接跑是没有问题的,因为比你小的不会被你统计到,所以就相当于删了。

Code

cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
ll ans = 0;
for (int i = 1; i <= n; i++) {
    int l = 0, r = 0;
    for (int j = 1; j < i; j++) l += (a[j] > a[i]);
    for (int j = i + 1; j <= n; j++) r += (a[j] > a[i]);
    ans += min(l, r);
}
cout << ans << "\n";

E

先鸽了。

posted @ 2025-08-06 14:58  Dtwww  阅读(17)  评论(0)    收藏  举报