CF Round 768 Div2 题解

A题 Min Max Swap

\(T\) 组数据。(\(1\leq T \leq 100\)

给定两个长度为 \(n\) 的数列 \(\{a_n\},\{b_n\}\),你现在可以进行不限量次操作,每次选择一个 \(p(1\leq p\leq n)\),并交换 \(a_p,b_p\),问怎样可以使得两个数列的分别的最大值的乘积最大(不输出方案,只输出最大值)?

\(1\leq n\leq 100,1\leq a_i,b_i \leq 10^4\)

有一个显然的思路:找出最大值所在的数组,然后让另外一个数组里面的值尽可能小即可。

简化一下,就是:把每个位置上面小的数放在 a 里面,大的数放在 b 里面,遍历一次即可,复杂度 \(O(n)\)

#include<bits/stdc++.h>
using namespace std;
const int N = 110;
int n, a[N], b[N];
int solve() {
    //read
    cin >> n;
    for (int i = 1; i <= n; ++i) cin >> a[i];
    for (int i = 1; i <= n; ++i) cin >> b[i];
    //solve
    for (int i = 1; i <= n; ++i)
        if (a[i] > b[i]) swap(a[i], b[i]);
    int Max1 = 0, Max2 = 0;
    for (int i = 1; i <= n; ++i)
        Max1 = max(Max1, a[i]), Max2 = max(Max2, b[i]);
    return Max1 * Max2;
}
int main()
{
    int T;
    cin >> T;
    while (T--) cout << solve() << endl;
    return 0;
}

B题 Fun with Even Subarrays

\(T\) 组数据。(\(1\leq T \leq 2*10^4\)

给定一个长度为 \(n\) 的数组 \(\{a_n\}\),我们可以进行如下操作:

选择一个长度为 \(2k\) 的区间,然后将后 \(k\) 个数字平移到前 \(k\) 个位置上面(后 \(k\) 个位置上面值不改变),详情可以见原题。

问,至少需要多少次操作,可以使得整个数组的值变成一样?

\(\sum n \leq 2*10^5,1\leq a_i\leq n\)

显然,最后一个位置的值不可能被变成其他值,那么我们可以将数组转化一下,如果原位置的值等于 \(a_n\) 就记为 0,反之记为 1。那么问题就变成了:怎么在最快状态下使得整个数组变为 0?

我们直接贪心:记一个指针 \(p\),从 \(n\) 开始,不断右移,当 \([p,n]\) 内部不是全 0 时,给 p 加上 1,然后记 \(L=\max(1, 2p-(n+1))\),然后将区间 \([L,p-1]\) 上面全部置为 0,同时 res 加上 1,再让 \(p=L-1\),直到 \(p=0\) 为止,此时 res 的值就是最少操作次数。

关于怎么判断一个区间是不是全 0,以及怎么快速修改每个位置的值,我考场上套的树状数组,总复杂度 \(O(n\log n)\),其实直接遍历一遍就行了,复杂度 \(O(n)\)

//树状数组版本代码
#include<bits/stdc++.h>
using namespace std;
const int N = 200010;
int n, a[N];
//
int t[N];
inline int lowbit(int x) { return x & (-x); }
int ask(int x) {
    int res = 0;
    for (; x; x -= lowbit(x)) res += t[x];
    return res;
}
void add(int x, int val) {
    for (; x <= n; x += lowbit(x)) t[x] += val;
}
int query(int l, int r) {
    return ask(r) - ask(l - 1);
}
//

void change(int l, int r) {
    for (int i = l; i <= r; ++i)
        if (query(i, i) == 1) add(i, -1);
}
int solve() {
    //read
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i)
        scanf("%d", &a[i]);
    //solve
    memset(t, 0, sizeof(int) * (n + 1));
    for (int i = 1; i <= n; ++i)
        if (a[i] != a[n]) add(i, 1);

    int res = 0, p = n - 1;
    while (p >= 1) {
        if (query(p, n) > 0) {
            p++;
            int L = max(1, 2 * p - n - 1);
            change(L, p - 1);
            p = L - 1;
            res++;
        }
        else p--;
    }
    return res;
}
int main()
{
    int T;
    scanf("%d", &T);
    while (T--) printf("%d\n", solve());
    return 0;
}
#include<bits/stdc++.h>
using namespace std;
const int N = 200010;
int n, a[N];
void change(int l, int r) {
    for (int i = l; i <= r; ++i) a[i] = 0;
}
int solve() {
    //read
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i)
        scanf("%d", &a[i]);
    //solve
    for (int i = 1; i <= n; ++i)
        a[i] = a[i] != a[n];

    int res = 0, p = n - 1;
    while (p >= 1) {
        if (a[p] > 0) {
            p++;
            int L = max(1, 2 * p - n - 1);
            change(L, p - 1);
            p = L - 1;
            res++;
        }
        else p--;
    }
    return res;
}
int main()
{
    int T;
    scanf("%d", &T);
    while (T--) printf("%d\n", solve());
    return 0;
}

C题 And Matching

\(T\) 组数据。(\(1\leq T \leq 400\)

给定 \(n\) 个数 \([0,n)\),保证 \(n\) 是 2 的幂,问能否给这 \(n\) 个数找到一种配对方式,使得 \(\sum\limits_{i=1}^{n/2}a_i\&b_i=k\),并输出该方案?

\(4\leq n \leq 2^{16},0\leq k < n\)

经过长时间观察,我发现了一种 \(k=0\) 时的构造方案:\((0,n-1),(1,n-2),\cdots,(\frac{n}{2}-1,\frac{n}{2})\) (这种方案是纯观察到的,但是存在性不容置疑:对每个数,直接寻找按位反差的那个值来配对即可)

如果 \(0 < k < n-1\),我们发现找出下面两组: \((0,n-1),(k,n-k-1)\),然后交换一下,变为 \((0,n-k-1),(k,n-1)\)即可,别的值不变,这两组的值之和增加了 \(k\) ,刚好满足要求。

\(k=n-1\) 的时候,我一开始以为无解,然后 WA 了几发之后老老实实去找方案:我们选择 \((0,n-1),(1,n-2)\) 两组变换为 \((0,1),(n-2,n-1)\),这样可以增加 \(n-2\);随后,我们选择 \((2,n-3),(3,n-4)\) 两组,变化为 \((2,n-4),(3,n-3)\)。从二进制上面不难发现,仅有最后一位出现了变化,其他位置保持不变,所以答案增加 1,刚好凑成 \(n-1\),成功。(\(n=4\) 的时候无解,因为此时仅有两组,不够分)

#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
int vis[N];
void solve(int n, int k) {
    if (k == n - 1) {
        if (n == 4) puts("-1");
        else {
            printf("%d %d\n%d %d\n%d %d\n%d %d\n", 0, 1, n - 2, n - 1, 2, n - 4, 3, n - 3);
            for (int i = 4; i < n / 2; ++i)
                printf("%d %d\n", i, n - 1 - i);
        }
        return;
    }
    memset(vis, 0, sizeof(int) * (n + 1));
    if (k) {
        vis[0] = vis[n - 1] = vis[k] = vis[n - k - 1] = 1;
        printf("%d %d\n%d %d\n", 0, n - k - 1, k, n - 1);
    }
    for (int i = 0; i < n / 2; ++i)
        if (!vis[i]) printf("%d %d\n", i, n - i - 1);
    return;
}
int main()
{
    int T;
    cin >> T;
    while (T--) {
        int n, k;
        cin >> n >> k;
        solve(n, k);
    }
    return 0;
}

D题 Range and Partition

\(T\) 组数据。(\(1\leq T\leq 3*10^4\)

给定一个长度为 \(n\) 的数列 \(\{a_n\}\) 和正整数 \(k\),问能否找到一个区间 \([x,y]\),使得我们能将数列分成恰好 \(k\) 段,每段里面,在该区间内部的数要比不在该区间的数要多?若存在,请输出 \(y-x\) 最小值。

\(1\leq k\leq n\leq 2*10^5,1\leq a_i\leq n\)

存在性毋庸置疑:让这个区间为 \([1,n]\) 即可,然后想怎么分。

我们先来看看,在确定了区间之后,如何判断该区间是否合法:我们构建一个新数组 \(\{b_n\}\),原数组中在该区间内部的数标记为 1,反之标记为 0,那么问题就变成了:给定一个 01 数组,问能否恰好分成 \(k\) 组,每组内部 1 的数量都大于 0?

今年牛客冬令营的第一场 F 题有一个类似的题目,问能否分成若干段,使得每段中位数都大于某个值。那场的题解和标准证明都在这:题解,我们直接搬运一下结论:当区间内 1 的个数减去 0 的个数大于等于 \(k\) 时存在分组方案,直接扫一遍,复杂度 \(O(n)\)。(本题还需要输出方案,道理也差不多

如果我们暴力枚举 \(x,y\),那么总复杂度就是 \((n^3)\),如果枚举 \(x\) 并二分 \(y\),那么复杂度就是 \(O(n^2\log n)\)

上面因为搬运结论,刚给忘了一个东西:我们并不需要扫一遍整个数组,只需要先给排个序,然后每次统计数量的时候直接两次二分即可,这样复杂度就降到了 \(O(n \log^2 n)\),应该可以通过了。

#include<bits/stdc++.h>
using namespace std;
const int N = 200010;
int n, k, a[N], b[N];
bool check(int x, int y) {
    int l = lower_bound(a + 1, a + n + 1, x) - a;
    int r = upper_bound(a + 1, a + n + 1, y) - a - 1;
    return (r - l + 1) - (n - (r - l + 1)) >= k;
}
void output(int x, int y) {
    printf("%d %d\n", x, y);
    int L = 1;
    for (int i = 1; i < k; ++i) {
        int cnt = 0;
        for (int R = L; R <= n; ++R) {
            x <= b[R] && b[R] <= y ? cnt++ : cnt--;
            if (cnt > 0) {
                printf("%d %d\n", L, R);
                L = R + 1;
                break;
            }
        }
    }
    printf("%d %d\n", L, n);
}
void solve()
{   //read & init
    scanf("%d%d", &n, &k);
    for (int i = 1; i <= n; ++i)
        scanf("%d", &a[i]), b[i] = a[i];
    sort(a + 1, a + n + 1);
    //solve
    int res = 1e9 + 10, ansx = 0, ansy = 0;
    for (int x = 1; x <= n; ++x) {
        int l = x, r = n + 1;
        while (l < r) {
            int mid = (l + r) >> 1;
            if (check(x, mid)) r = mid;
            else l = mid + 1;
        }
        if (r != n + 1 && res > r - x) {
            res = r - x, ansx = x, ansy = r;
        }
    }
    //output
    output(ansx, ansy);
}
int main()
{
    int T;
    scanf("%d", &T);
    while (T--) solve();
    return 0;
}

不过我们还发现:\(x,y\) 均符合单调性质,所以我们还可以双指针,将枚举 \(x,y\) 的复杂度降到 \(O(n)\),这样就可以在 \(O(n\log n)\) 的复杂度内解决该问题了。

其实我们甚至不用直接枚举 \(x,y\),而是直接枚举对应的下标:在排序好的数组上枚举 \(i,j\),保证 \(i,j\) 之间的数要多于不在该范围的数,然后不断更新 \(x=a_i,y=a_j\) 即可。这个 \(i,j\) 甚至不需要双指针,直接预先算好那个合适的距离 \(len=\lceil \frac{n+k}{2}\rceil\) 即可。

#include<bits/stdc++.h>
using namespace std;
const int N = 200010;
int n, k, a[N], b[N];
void output(int x, int y) {
    printf("%d %d\n", x, y);
    int L = 1;
    for (int i = 1; i < k; ++i) {
        int cnt = 0;
        for (int R = L; R <= n; ++R) {
            x <= b[R] && b[R] <= y ? cnt++ : cnt--;
            if (cnt > 0) {
                printf("%d %d\n", L, R);
                L = R + 1;
                break;
            }
        }
    }
    printf("%d %d\n", L, n);
}
void solve()
{   //read & init
    scanf("%d%d", &n, &k);
    for (int i = 1; i <= n; ++i)
        scanf("%d", &a[i]), b[i] = a[i];
    //solve
    sort(a + 1, a + n + 1);
    int res = 1e9 + 10, x = 0, y = 0;
    int len = (n + k + 1) / 2;
    for (int L = 1; L + len - 1 <= n; ++L) {
        int R = L + len - 1;
        if (res > a[R] - a[L])
            res = a[R] - a[L], x = a[L], y = a[R];
    }
    //output
    output(x, y);
}
int main()
{
    int T;
    scanf("%d", &T);
    while (T--) solve();
    return 0;
}
posted @ 2022-02-07 12:09  cyhforlight  阅读(27)  评论(0编辑  收藏  举报