AtCoder Beginner Contest 136 解题报告

转载自:https://lornd.top/index.php/archives/30/

比赛地址:AtCoder Beginner Contest 136

A - Transfer

题目大意

有两个瓶子,第一个瓶子的容量为 \(A\) ,含有水的体积为 \(B\) ,第二个瓶子含有水的体积为 \(C\) 。问第二个瓶子中最少要剩余多少水。

解题思路

贪心,不断将第二个瓶子中的水倒入第一个瓶子,直到第一个瓶子倒满。

#include <cstdio>
#include <algorithm>

int a, b, c;

int main() {
    scanf("%d%d%d", &a, &b, &c);
    printf("%d", std::max(0, c - (a - b)));
    return 0;
}

B - Uneven Numbers

题目大意

给定一个数 \(n\) ,问有多少个不大于 \(n\) 的正整数由奇数个数位组成。

解题思路

我们预处理出一位数、三位数和五位数的个数,然后判断 \(n\) 是几位数,再特判掉即可。

#include <cstdio>

int n, cnt, ans;
int num[6] = {0, 9, 90, 900, 9000, 90000};

int NumerofDigits(int key) {
    int tmp = 0;
    while(key) {
        key /= 10;
        ++tmp;
    }
    return tmp;
}

int main() {
    scanf("%d", &n);
    cnt = NumerofDigits(n);
    for (int i = 1; i < cnt; i += 2) {
        ans += num[i];
    }
    if (cnt == 5)
        ans += (n - 10000 + 1);
    else if (cnt == 3)
        ans += (n - 100 + 1);
    else if (cnt == 1)
        ans += n;
    printf("%d", ans);
}

C - Build Stairs

题目大意

给你一个长度为 \(n\) 的序列,对于每一个元素,可以将其减少 \(1\) 或者什么都不做,问能否将这个序列变成不下降序列。

解题思路

因为我们只能将一个数变小,所以我们从后往前处理,如果一个数大了,就把它变小,如果变小了依然不满足要求,那就说明这个操作无法完成了。

#include <cstdio>

const int MAXN = 1e5 + 5;

int n;
int height[MAXN];

bool judge() {
    for (int i = n - 1; i; --i) {
        if (height[i] > height[i + 1])
            --height[i];
        if (height[i] > height[i + 1])
            return false;
    }
    return true;
}

int main() {
    scanf("%d", &n);
    for (int i = 1; i<= n; ++i) {
        scanf("%d", &height[i]);
    }
    if (judge())
        printf("Yes");
    else
        printf("No");
    return 0;
}

D - Gathering Children

题目大意

在一个一维地图上,一共有 \(n\) 个点,每个点都写着 \(L\)\(R\) ,分别代表从这个点出发要向左走还是向右走一步,最开始每个点都有一个孩子,每一次行动每个孩子都会走一步,问经过 \(10^{100}\) 次行动之后,每个点有多少个孩子。

解题思路

由于操作次数足够多,每个孩子最终一定会在标有 \(L\)\(R\) 的两个点之间徘徊,所以我们直接模拟每个孩子的行走过程,遇到 \(L\)\(R\) 相邻的点就判断行动偶数次之后停留在哪个点(\(10^{100} 是偶数\))就可以了。

但这个算法的最坏复杂度是 \(\Theta (n ^ 2)\) 的,对于 \(10^5\) 的数据范围无能为力,所以需要优化,卡的地方事实上就是多个 \(L\)\(R\) 相连,那这个时候所有的 \(L\)\(R\))本质是相同的,在这些点上的孩子全部会到同一个 \(L\)\(R\) 交界处,所以我们可以开一个数组记录在某一个位置的孩子最终会到哪个交界处,以后连续的访问到这个点就可以直接跳到交界处,而不需要一个一个遍历,这样我们就几乎把这个算法优化到了 \(\Theta (n)\) ,从而可以完美地解决这个问题了。

#include <cstdio>
#include <cstring>

const int MAXN = 1e5 + 5;

int n;
char s[MAXN];
int num[MAXN], left[MAXN], right[MAXN];

int main() {
    scanf("%s", s + 1);
    n = strlen(s + 1);
    for (int i = 1; i <= n; ++i) {
        if (s[i] == 'L') {
            for (int j = i - 1; j; --j) {
                while (left[j]) {
                    j = left[j];
                }
                if (s[j] == 'R') {
                    left[i] = j;
                    if ((i - j) & 1)
                        ++num[j + 1];
                    else
                        ++num[j];
                    break;
                }
            }
        }
    }
    for (int i = n; i; --i) {
        if (s[i] == 'R') {
            for (int j = i + 1; j <= n; ++j) {
                while (right[j])
                    j = right[j];
                if (s[j] == 'L') {
                    right[i] = j;
                    if ((j - i) & 1)
                        ++num[j - 1];
                    else
                        ++num[j];
                    break;
                }
            }
        }
    }
    for (int i = 1; i <= n; ++i) {
        printf("%d ", num[i]);
    }
    return 0;
}

E - Max GCD

题目大意

给定一个长度为 \(n\) 的序列,有一种操作:选择序列中任意两个元素,分别对它们进行加 \(1\) 或减 \(1\) 的操作,问通过最多 \(K\) 次这样的操作,的到的序列所有元素的最大公约数是多少。

解题思路

首先:我们有以下结论:

  1. 操作的意义是在序列总和不变的情况下重排这个序列。

  2. 最大公约数一定是序列中所有元素总和的约数。

根据数据范围,可以知道总和不会超过 \(5 \times 10^8\)小于这个数的数的约数个数事实上是很少的,这是我们解决这题的关键,可以自己动手说明(要使得一个数的约数尽可能多,就要使得其质因子尽可能多样化,但是这样会导致得到的数爆炸式增长,很快就会超过总和上限)

那么在 \(\Theta (\sqrt{sum})\) 的时间内得到总和的约数之后,我们可以从大到小检查每一个约数是否可行,检查到第一个可行的约数时就返回答案。

那么如何判断一个约数 \(i\) 是否可行呢?

我们要将每一个数都调整成 \(i\) 的倍数,每个数或加或减,而且加的总和等于减的总和,为了表述方便,我们把将一个数向下调整 \(1\) 称为 “减操作” ,把一个数向上调整称 \(1\) 为 “加操作” 。

我们不妨先对每一个元素都进行减操作,然后再把一些减操作分给加,由于要最小化操作次数,所以最开始减的操作次数是 \(cnt = \sum\limits_{j = 1} ^ n (a_j\ Mod\ i)\)并且 \(cnt\) 一定是 \(i\) 的倍数, 同时如果把一个元素的减操作改成加操作,那么减操作数和加操作数的差会减少 \(i\)。因此我们直接利用 \(cnt\) 除以 \(i\) 算出要变化 \(k\) 次,然后再不断将消耗减操作次数最多改成加操作(注意由于一个减操作必然对应一个加操作,所以增加的加操作是不用记录到总次数中去的),这样我们就最小化了调整的次数,再判断操作次数与 \(K\) 的大小即可。

#include <cstdio>
#include <algorithm>

const int MAXN = 1e5 + 5;

int n, k, sum, cnt;
int arr[MAXN], ans[MAXN], more[MAXN];

bool judge(int key) {
    int diff = 0, need;
    for (int i = 1; i <= n; ++i) {
        more[i] = arr[i] % key;
        diff += more[i];
    }
    need = diff;
    diff /= key;
    diff = n - diff + 1;
    std::sort(more + 1, more + n + 1);
    for (int i = n; i >= diff; --i) {
        need -= more[i];
    }
    return k >= need;
}

int main() {
    scanf("%d%d", &n, &k);
    for (int i = 1; i <= n; ++i) {
        scanf("%d", &arr[i]);
        sum += arr[i];
    }
    for (int i = 1; i * i <= sum; ++i) {
        if (sum % i == 0) {
            int tmp = sum / i;
            ans[++cnt] = tmp;
            if (tmp != i)
                ans[++cnt] = i;
        }
    }
    std::sort(ans + 1, ans + cnt + 1);
    for (int i = cnt; i; --i) {
        if (judge(ans[i])) {
            printf("%d", ans[i]);
            break;
        }
    }
    return 0;
}

F - Enclosed Points

题目大意

平面直角坐标系上有 \(n\) 个点,每次选择若干个点组成子集,得到能够将这个子集中的点能够全部包含的最小矩形(边与坐标轴平行),计算这个矩形中包含的点数,计算所有子集的答案的和。

特殊性质:点的横坐标各不相同,纵坐标各不相同。

解题思路

显然我们不可能去枚举子集,但是我们可以转化问题,去数每个点被多少个子集包含,相加同样可以得到结果。

如何计算点 \(i\) 被多少个子集包含呢?

我们先假设 \(i\) 被所有子集包含,再减去不会包含它的子集,由于知道元素总个数可以直接算出子集个数(注意要减去空集),所以我们只关心点的个数。

如图所示,对于一个点 \(i\) ,我们可以以点 \(i\) 为中心,将平面直角坐标系分成四个部分,所有点都在 \(A\bigcup B\) 、$B \bigcup D $ 、\(A \bigcup C\)\(C \bigcup D\) 区域的子集不会包含点 \(i\) ,减去即可,由于特殊性质,我们可以发现对于第 \(i\) 个点,\(|A \bigcup C| = |C \bigcup D| = i - 1\)\(|A\bigcup B| = |C \bigcup D| = n - i\) 。将这些情况减去即可。

但是在这个过程中所有点都在 \(A、B、C、D\) 的点均被减去了两次,所以要加回来,对于 \(C\) 区域的点,就是简单的二维数点问题 。对于 \(A、D\) 区域的点,根据之前得到的关系可以计算,事实上也就可以算出 \(B\) 区域的点了,但是代码中用的是坐标变换把 \(B\) 区域变成了 \(C\) 区域,再用一次二维数点。把这些情况加上即可。

#include <cstdio>
#include <cstring>
#include <algorithm>

const int MAXN = 2e5 + 5;
const int mod = 998244353;

int n, ans;
int pow[MAXN];
int res[MAXN], BIT[MAXN];

struct num {
    int key, place;

    num(int kk = 0, int pp = 0) {
        key = kk;
        place = pp;
    }
    
    bool operator < (const num &another) {
        return key < another.key;
    }
}arr[MAXN];

struct node {
    int x, y;

    node (int xx = 0, int yy = 0) {
        x = xx;
        y = yy;
    }
    
    bool operator < (const node &another) const {
        if (x == another.x)
            return y < another.y;
        else
            return x < another.x;
    }
}point[MAXN];

struct BITree {
    int BIT[MAXN];

    void clear() {
        memset(BIT, 0, sizeof(BIT));
    }

    int lowbit(int key) {
        return key & (-key);
    }

    void modify(int place, int key) {
        for (; place <= n; place += lowbit(place)) {
            BIT[place] += key;
        }
    }

    int query(int place) {
        int tmp = 0;
        for (; place; place -= lowbit(place)) {
            tmp += BIT[place];
        }
        return tmp;
    }
}root;

void discretizate() {
    int counter = 1;
    std::sort(arr + 1, arr + n + 1);
    for (int i = 1; i <= n; ++i) {
        res[arr[i].place] = i;
    }
    for (int i = 1; i <= n; ++i) {
        point[i].y = res[i];
    }
}

int main() {
    pow[0] = 1;
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i) {
        scanf("%d%d", &point[i].x, &point[i].y);
        arr[i] = num(point[i].y, i);
        pow[i] = (pow[i - 1] << 1) % mod;
    }
    ans = 1ll * n * (pow[n] - 1) % mod;
    for (int i = 1; i <= n; ++i) {
        ans = (ans - (pow[i - 1] - 1) + mod) % mod;
        ans = (ans - (pow[n - i] - 1) + mod) % mod;
        ans = (ans - (pow[i - 1] - 1) + mod) % mod;
        ans = (ans - (pow[n - i] - 1) + mod) % mod;
    }
    discretizate();
    std::sort(point + 1, point + n + 1);
    for (int i = 1; i <= n; ++i) {
        int tmp = root.query(point[i].y);
        ans = (ans + (pow[tmp] - 1)) % mod;
        ans = (ans + (pow[i - 1 - tmp] - 1)) % mod;
        root.modify(point[i].y, 1);
    }
    root.clear();
    for (int i = n; i; --i) {
        int tmp = root.query(n + 1 - point[i].y);
        ans = (ans + (pow[tmp] - 1)) % mod;
        ans = (ans + (pow[n - i - tmp] - 1)) % mod;
        root.modify(n + 1 - point[i].y, 1);
    }
    printf("%d", ans % mod);
    return 0;
}
posted @ 2019-08-11 11:19  lornd  阅读(270)  评论(0编辑  收藏  举报