CF Round #703 Div2 解题补题报告

官方题解](https://codeforces.com/blog/entry/87849)

A题 Shifting Stacks (思维,前缀和)

给定 \(n\) 堆石头堆,第 \(i\) 堆上面有 \(h_i\) 块石头。

现在可以进行多次操作,每次可以将第 \(i\) 堆上面拿走一块石头放到第 \(i+1\) 堆上面(别问能不能拿走最后一堆上面的),可以重复在某一堆上面拿,直到拿空。

问能否在多次操作之后使得 \(h_i\) 呈严格单调递增。

\(1\leq n \leq 100,0 \leq h_i \leq 10^9\)

有个比较显然的性质:最小的且满足要求的数列,是 \(\text{0, 1, 2, ..., n-1}\)

如果前面堆上的石头较多,那么总是可以将其转移到后者上面。

那么就有一种比较清晰的思路:从前向后扫一遍,依次判断是否满足 \(\sum\limits_{i=1}^{k}h_i \geq \frac{k(k-1)}{2}\)

如果都能满足则输出 \(YES\) ,反之输出 \(NO\)

#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 110;
int n, h[N];
bool solve()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i)
        scanf("%d", &h[i]);
    LL sum = 0;
    for (LL i = 1; i <= n; ++i) {
        sum += h[i];
        if (sum * 2 < i * (i - 1)) return false;
    }
    return true;
}

int main()
{
    int T;
    scanf("%d", &T);
    while (T--)
        puts(solve() ? "YES" : "NO");
    return 0;
}

B题 Eastern Exhibition (排序,中位数)

给定 \(n\) 个房子的坐标,第 \(i\) 个房子的坐标为 \((x_i,y_i)\) 。(所有横纵坐标的值均为整数)

现在想要建一个剧院,该剧院到第 \(i\) 个房子的曼哈顿距离为 \(dist(x)\) ,请求出有多少个坐标,使得剧院建在这个坐标上面时,使得 \(\sum\limits_{i=1}^{n}dist(i)\) 最小?

\(1 \leq n \leq 1000,0 \leq x_i,y_i \leq 10^9\)

如果这题是一维的,那么这题就是很明显的 货舱选址 问题,直接排序,然后选中位数或者最中间两点之间的坐标即可,证明略(高中都手玩过这种 \(f(x)=|x-a|+|x-b|+|x-c|+...\) 这种函数吧?就在中间取到最小值)

那么二维呢?实际上,曼哈顿距离中,\(x,y\) 两者是线性无关的,所以在两个维度上面都选一次就好了,根据乘法原理乘一下就行了。实际上,不仅可以求二位,我们还可以求更高维度。

#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 1010;
int n;
LL x[N], y[N];
void solve()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i)
        scanf("%lld%lld", &x[i], &y[i]);

    sort(x + 1, x + n + 1);
    sort(y + 1, y + n + 1);
    LL ans = (n % 2 == 1) ? 1 : 
    (x[n/2 + 1] - x[n/2] + 1) * (y[n/2 + 1] - y[n/2] + 1);
    printf("%lld\n", ans);
}

int main()
{
    int T;
    scanf("%d", &T);
    while (T--) solve();
    return 0;
}

C1题 Guessing the Greatest (easy version) (交互,二分)

这是一道交互题。

已知一个长度为 \(n\) 的数列 \(\{a_n\}\) ,我们在一开始只知道数列的长度。

我们每次可以向系统询问一段区间 \([l,r](l<r)\) 内第二大元素的下表,现在我们需要经过多次询问,找出该数列中最大元素的下标。

询问次数不得超过 \(40\) 次。

\(2\leq n \leq10^5\)

第二次在 \(CodeForces\) 上面见到交互题了,而且还是二分(逃

上一次还是 Round #700 Div2

和上次那个惨不忍睹的题目比起来,这题好写多了(反之我这么想的),虽然也踩了一堆坑

这个数据规模,这个查询限制,很显然是二分了,现在看看咋二分吧

对于区间 \([l,r]\) ,我们分别令 \(i=query(l,r),j=query(l,mid),k=query(mid+1,r)\)

那么很显然,如果 \(i=j\),那么说明最大值就在区间 \([l,mid]\) 内,如果 \(i=k\),那么最大值就在区间 \([mid+1,r]\) 内。

如果 \(i\not=j,i\not=k\) 呢?

我第一次就被这卡了,导致我去想了其他思路(逃

实际上呢,上面的思路已经比较接近正确了,我们找几组不太符合的情况,自己手玩一下,就可以修改成正确思路了:

\(i=query(l,r)\)

  1. 如果 \(l \leq i \leq mid\) 的话,令 \(j=query(l,mid)\),如果 \(i=j\) ,那么最大值就在 \([l,mid]\) 中,反之则在 \([mid+1,r]\)
  2. 如果 \(mid+1 \leq i \leq r\) 的话,令 \(j=query(mid+1,r)\),如果 \(i=j\) ,那么最大值就在 \([mid+1,r]\) 中,反之则在 \([l,mid]\)

遵循这个思路,我们就可以 A 掉这个 \(\text{Easy Version}\) 了(逃

整个询问的次数应该在 \(2*\log_2^{10^5}=2*17=34\) 次这样

#include<bits/stdc++.h>
using namespace std;

int query(int l, int r) {
    printf("? %d %d\n", l, r);
    fflush(stdout);
    int index;
    scanf("%d", &index);
    return index;
}
void output(int x) {
    printf("! %d\n", x);
    fflush(stdout);
}
void solve_2(int l, int r) {
    int x = query(l, r);
    output(x == l ? r : l);
}
void solve_3(int l, int r) {
    int x = query(l, r);
    if (x == l) solve_2(l + 1, r);
    else if (x == r) solve_2(l, r - 1);
    else {
        if (query(l, l + 1) == x) solve_2(l, l + 1);
        else solve_2(r - 1, r);
    }
}
int main()
{
    int n;
    scanf("%d", &n);
    int l = 1, r = n;

    while (l < r) {
        if (r - l == 1 || r - l == 2) {
            if (r - l == 1) solve_2(l, r);
            if (r - l == 2) solve_3(l, r);
        }
        int mid = (l + r) >> 1;
        int i = query(l, r), j;
        if (l <= i && i <= mid) {
            j = query(l, mid);
            if (i == j) r = mid;
            else l = mid + 1;
        }
        else {
            j = query(mid + 1, r);
            if (i == j) l = mid + 1;
            else r = mid;
        }
    }
    printf("! %d\n", l);
    fflush(stdout);
    return 0;
}

C2题 Guessing the Greatest (hard version)(交互,二分)

和上一题区别在于,这题的询问次数被卡到了 \(20\) 次。

上一题的代码到这会 \(WA\),我尝试着保存了一些可以维护的状态,但是还是没法控制住询问的次数。那么理论上每次二分只能询问一次了。

搬运一下官方题解的思路:

显然我们先通过上面的思路,先初步确定一下答案区间,例如说答案区间在 \([x,r]\) 上面

我们假设最大值的下标为 \(y\),那么当 \(x < i < y\) 时,\(query(x,i)=?\) (不管是多少,反之不是 \(x\)),而 \(y \leq i \leq r\) 时,\(query(x,i)=x\),这个证明是很显然的,不再赘述

突然发现,如果我们二分 \(y\) ,好像效率会更高些?

总体询问次数为 \(2+\log_2^{10^5}=19\) ,可以过!

代码不给了,感兴趣可以看官方题解(逃

D题 Max Median (二分)

给定一个长度为 \(n\) 的数列 \(\{a_n\}\) ,尝试找出一组长度不短于 \(k\) 的连续子数列,使得其中位数最大。

中位数的定义(和标准可能有所出入):长度为 \(N\) 的数列,其升序排列后的第 \(\lfloor \frac{N+1}{2} \rfloor\) 个元素,为该数列的中位数。

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

官方题解写的太好了,我直接搬运(翻译)一下得了,就不造轮子了:

  1. 如果 \(\{a_n\}\) 中只有 \(0\)\(1\) ,我们咋选?

    显然,应该选择一个长度不小于 \(k\) 的区间段,使得其中 \(0\) 的数量少于 \(1\) ,这样才能使得中位数是 \(1\) 而非 \(0\)

    我们也可以将 \(0\) 改为 \(-1\) ,问题就变成了如何使区间和为正数了。

    直接求区间和的最大值,那么我们直接枚举 \(r\) ,然后找 \(sum_l\) 最小值,然后相减一下,看看能不能使得区间值大于 \(0\) 的 。朴素复杂度 \(O(n^2)\) ,数据结构优化 \(O(n\log n)\) ,线性递推复杂度 \(O(n)\)

  2. 问题是,现在这数列里面的数也不只 \(0\)\(1\) 啊?

    我们可以抽象一下,\(0\)\(1\) 是典型的布尔值,那么对于数列里面的每个数,我们可以对其进行一个布尔表达式的运算,使得结果为 \(0\) 或者 \(1\) 即可(也就是将数分成两类)。

  3. 所以表达式是啥?

    题目要求中位数最大,那我们的表达式应该也和大小有关系。不妨设定一个数 \(x\) ,如果 \(a_i\geq x\) 就将其标记为 \(1\),反之标记为 \(0\) 。显然 \(\min\limits_{1 \leq i \leq n}a_i\leq x \leq \max\limits_{1 \leq i \leq n}a_i\)

  4. 所以说?

    对于 \(x\) 构造出来的新数列,如果我们可以找出一个中位数为 \(1\) 的子数列,说明这个子数列在原数列中的对应数列,他的中位数是不小于 \(x\) 的。

    对于这个 \(x\),我们可以枚举,但是,我们似乎还可以使用一些效率更高的方法,例如 \(......\) 二分?

对中位数 \(x\) 在值域上进行二分,每次线性判定,总复杂度 \(O(n \log{n})\)

#include<bits/stdc++.h>
using namespace std;
const int N = 200010;
int n, k, a[N], sum[N];
inline int b(int i, int x) { return (a[i] >= x) ? 1 : -1; }
bool solve(int x) {
    for (int i = 1; i <= n; ++i)
        sum[i] = sum[i - 1] + b(i, x);

    int Min = N, Max = - N;
    for (int i = k; i <= n; ++i) {
        Min = min(Min, sum[i - k]);
        Max = max(Max, sum[i] - Min);
    }
    return (Max > 0);
}
int main()
{
    scanf("%d%d", &n, &k);
    for (int i = 1; i <= n; ++i)
        scanf("%d", &a[i]);
    int l = 0, r = n;
    while (l < r) {
        int mid = (l + r + 1) >> 1;
        if (solve(mid)) l = mid;
        else r = mid - 1;
    }
    printf("%d", l);
    return 0;
}
posted @ 2021-02-20 17:04  cyhforlight  阅读(133)  评论(0编辑  收藏  举报