Codeforces Round 1111 (Div. 2)

没有 E 和 F。

A. Zero Sum

考虑 +1 和 -1 数量的差值,每次操作会让差值 +4, -4 或者不变,操作目标是让差值等于 0。

如果初始差值模 4 不等于 0,后续操作就不可能让差值等于 0;反之,一定存在让差值等于 0 的操作序列,要细究的话策略很好想,题目也只让判断是否可行,就不展开了。

B. Yet Another Constructive

首先考虑无解情形,由于所有元素必须为正整数,显然 \(m < k\) 时无解(就算 k 个元素全 1,加起来都比 m 大了)。

接下来考虑如何构造题意要求的数组,一个很 trivial 的想法是把第一个数填得很大,后面全填 1,这样能让第一个子数组的和取到 \(m\),而再往后滑动窗口的时候,和就不可能够到 m。至于第一个数填多少也很简单,简单计算可得 \(m - (k - 1)\)

C. Inversion of a Subsequence

操作限制翻译过来就是:每次取反操作里,子序列中至少要有一个 \(1\)

显然一次操作就可以把所有 \(0\) 变成 \(1\),接下来就是分类讨论了:

  • \(a\)\(b\) 数组完全一致:不用修改,答案为 0
  • \(a\)\(b\) 数组不一致,且 \(a\) 中不存在 \(1\):答案为 -1
  • \(a\)\(b\) 数组不一致,且 \(a\) 中存在 \(1\),有一个及以上的 \(1\) 要变成 \(0\):答案为 1
  • \(a\)\(b\) 数组不一致,且 \(a\) 中存在 \(1\),没有 \(1\) 要变成 \(0\),但存在 \(0\) 要变成 \(1\):答案为 2

想清楚之后对着写 if-else 条件判断就可以了。

D1. XOR Sorting (Easy Version)

这个思路不很好,想的时候路走窄了,虽然能过 D1,但是不好推广到 D2,不过还是记一下。

考虑如何以最小的代价将 \(a[i]\) 挪到 \(a[j]\)。trivial 的想法是直接一步交换它俩,但是或许有更省的方法。

\(\text{msb}(n)\) 为“\(n\) 的二进制最高位 \(1\) 的位置”,则:

  • 判断 \(i\)\(j\) 的二进制最高位 1 的位置
  • 如果 \(i\)\(j\) 的二进制最高位 1 的位置不同,假设 \(b_i = \text{msb}(i) > \text{msb}(j) = b_j\),以 \(2^{b_i}\) 的代价把 \(a[i]\) 挪到 \(a[i \oplus 2^{b_i}]\),即消除掉 \(i\) 的最高位 \(1\),然后回到第一步继续比较
  • 如果 \(i\)\(j\) 的二进制最高位 1 的位置相同,则可以先同时忽略这一位,继续回到第一步比较更低位

很容易能看出来,通过这个过程,把 \(a[i]\) 挪到 \(a[j]\) 的最小代价就是:

\[2^{\text{msb}(i \oplus j)} \]

比直接一步交换 \(a[i]\)\(a[j]\) 显然更优。

至于在这个过程中那些被改动了位置的数,其实不很重要。直觉上,把它们换回原位置的代价不会超过 \(2^{\text{msb}(i \oplus j)}\),考虑到答案只取最大代价,这些位置改变不会影响最终答案。

代码实现上倒是很简单,考虑对原数组排序,得到每个数的原 index(记为 \(i\))和排序后的目标 index(记为 \(j\)),枚举 \(2^{\text{msb}(i \oplus j)}\) 并取最大即可,时间复杂度就是排序的 \(n \log n\)

#include <bits/stdc++.h>
using namespace std;
 
typedef long long ll;
typedef long double ld;
typedef std::vector<int> vi;

#define INT_INF 2147483647
 
#define forn(i, a, b) for(int i = a; i < b; i++)
#define readi(x) scanf("%d", &x)
#define readv(x, n) forn(i,0,n) scanf("%d", &x[i])
 
#ifdef DEBUG
#define printi(x) printf("DEBUG: %d\n", x)
#define prints(x) printf("%s", x)
#define printv(x, n) vi x(n); printf("DEBUG: "); forn(i,0,n) printf("%d ", x[i])
#else
#define printi(x) printf("%d\n", x)
#define prints(x) cout << x << endl
#define printv(x, n) forn(i,0,n) printf("%d ", x[i])
#endif

int main(void){
    int t;
    readi(t);

    while (t--) {
        int n, _;
        readi(n);
        readi(_);

        vector<pair<int, int>> v;
        forn (i, 0, n) {
            int _tmp;
            readi(_tmp);
            v.push_back({_tmp, i});
        }

        sort(v.begin(), v.end());

        int ans = 0;
        forn (i, 0, n) {
            if (v[i].second != i) {
                ans = max(ans, (1 << __lg(v[i].second ^ i)));
            }
        }

        printi(ans);
    }
}

D2. XOR Sorting (Hard Version)

如果承接 D1 的思路考虑如何动态维护数组,会发现很困难:每次改变元素的时候,\(i\)(src index)倒是不会变,但是 \(j\)(dst index)会在一个子区间内造成 \(+1\)\(-1\) 的影响,快速更新一个子区间的 \(\text{msb}(i \oplus (j + 1))\) 或者 \(\text{msb}(i \oplus (j - 1))\) 在我的知识范围内是一个 non-trivial 的事情。

其实在推出答案一定是 2 的幂次之后,就该从答案取值入手来考虑问题。我们考虑当付出 \(2^k\) 的代价的时候,可以做到什么?答案是,对于所在位置的二进制表示,前缀相同但低 \(k\) 位不同的一组数,能够通过交换做到任意重排,进一步地,也就是可以对这个子区间完成排序。

如果想将两个排好序的子区间合并成一个排好序的大子区间,又需要付出多少代价?

假设两个子区间的大小均为 \(2^k\),合并之后的子区间大小则为 \(2^{k+1}\)。如果左半子区间的最大值比右半子区间的最小值要小,那只需要排序好这两个子区间,不需要额外的代价就能完成大子区间的排序;否则,则存在元素需要跨越两个子区间的边界,需要付出 \(2^{k+1}\) 的代价来重新排序合并之后的子区间。

于是对于每个长度为 \(2^k\) 的子区间,只需维护其最大最小值,即可推知其排序的最小代价,加上单点修改操作和上面提到的可合并特性,天然适合用线段树来维护。

注意这破题比较卡 I/O,不要用 scanf/printf,至少使用关闭流同步的 cin/cout,否则会超时。线段树本身的常数在这题倒不是什么大问题。

#include <bits/stdc++.h>
#include <bit>
using namespace std;
 
typedef long long ll;
typedef long double ld;
typedef std::vector<int> vi;

#define N 1000000
#define INT_INF 2147483647
 
#define forn(i, a, b) for(int i = a; i < b; i++)
#define readi(x) scanf("%d", &x)
#define readv(x, n) forn(i,0,n) scanf("%d", &x[i])
 
#ifdef DEBUG
#define printi(x) printf("DEBUG: %d\n", x)
#define prints(x) printf("%s", x)
#define printv(x, n) vi x(n); printf("DEBUG: "); forn(i,0,n) printf("%d ", x[i])
#else
#define printi(x) printf("%d\n", x)
#define prints(x) cout << x << endl
#define printv(x, n) forn(i,0,n) printf("%d ", x[i])
#endif

int x[N + 5], idx[N + 5];
int tree[4 * N + 5][3];

void build(int i, int l, int r, int n) {
    if (l == r){
        if (l <= n) {
            tree[i][0] = tree[i][1] = x[l];
            idx[l] = i;
        }
        else {
            tree[i][0] = tree[i][1] = INT_INF;
        }

        tree[i][2] = 0;
        return;
    }

    int mid = (l + r) >> 1;
    build(i << 1, l, mid, n);
    build(i << 1 | 1, mid + 1, r, n);

    tree[i][0] = max(tree[i << 1][0], tree[i << 1 | 1][0]);
    tree[i][1] = min(tree[i << 1][1], tree[i << 1 | 1][1]);
    tree[i][2] = ((tree[i << 1][0] > tree[i << 1 | 1][1]) ? ((r - l + 1) >> 1) : max(tree[i << 1][2], tree[i << 1 | 1][2]));

    return;
}

void update(int i, int l, int r, int i_j, int x_j) {
    if (l == r){
        tree[i][0] = tree[i][1] = x_j;
        tree[i][2] = 0;
        return;
    }

    int mid = (l + r) >> 1;
    if (i_j <= mid)     update(i << 1, l, mid, i_j, x_j);
    else                update(i << 1 | 1, mid + 1, r, i_j, x_j);

    tree[i][0] = max(tree[i << 1][0], tree[i << 1 | 1][0]);
    tree[i][1] = min(tree[i << 1][1], tree[i << 1 | 1][1]);
    tree[i][2] = ((tree[i << 1][0] > tree[i << 1 | 1][1]) ? ((r - l + 1) >> 1) : max(tree[i << 1][2], tree[i << 1 | 1][2]));

    return;
}
 
int main(void){
    cin.tie(0)->sync_with_stdio(false);

    int t;
    cin >> t;

    while (t--){
        int n, q;
        cin >> n >> q;

        forn (i, 1, n + 1)
            cin >> x[i];

        unsigned int new_n = bit_ceil((unsigned)n);
        build(1, 1, new_n, n);

        cout << tree[1][2] << '\n';
        while (q--) {
            int i_j, x_j;
            cin >> i_j >> x_j;
            update(1, 1, new_n, i_j + 1, x_j);
            cout << tree[1][2] << '\n';
        }
    }
}
posted @ 2026-07-25 02:38  Pride205  阅读(13)  评论(0)    收藏  举报