【解题报告】Codeforces Round #340 (Div. 2)

题目链接


A. Elephant(Codeforces 617A)

思路

因为是求最小行走次数,因此可以贪心地尽可能多走 5 步。其中走 5 步的次数可以通过 x5 求出来。而如果执行除法之后还有剩余的话( xmod5>0 )行走次数还要加 1 次。

代码

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

int x;

int main() {
    scanf("%d", &x);
    printf("%d\n", x / 5 + (x % 5 > 0));
    return 0;
}

B. Chocolate(Codeforces 617B)

思路

想象一下,在每两个 1 之间插入“隔板”就能够完成任务。那么有多少种方法呢?设第 i 1 和第 i+1 1 之间有 z[i] 0 。那么在这两个 1 之间插入“隔板”的方法有 z[i]+1 种。根据计数原理的乘法法则(总方法数等于每个步骤的方法数之积), ans=(z[i]+1)
需要注意的是序列的前缀 0 和尾缀 0 z[i] ,是不能计入累乘的。

代码

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

typedef long long ll;
const int maxn = 105;
int n, head;
ll cnt, ans, sum, a[maxn];

int main() {
    cin >> n;
    for(int i = 0; i < n; i++) {
        cin >> a[i];
        sum += a[i];
    }
    if(sum == 0) {
        puts("0");
        return 0;
    }
    // 找到序列中的第一个1
    for(head = 0; head < n; head++) {
        if(a[head] == 1) {
            break;
        }
    }
    ans = 1;
    // 统计连续的0的个数同时累乘
    for(int i = head; i < n; i++) {
        if(a[i] == 1) {
            ans *= (cnt + 1);
            cnt = 0;
        }
        else {
            cnt++;
        }
    }
    cout << ans << endl;
    return 0;
}

C. Watering Flowers(Codeforces 617C)

思路

显然圆心和与圆心不重合的点,这两点能且仅能刻画一个圆。设有 n 个点,那么对于圆心 O1 而言,可以通过这 n 个点构成 n 个圆,对于圆心 O2 而言也是如此。
这样,通过离散化思想我们就可以将问题转化为:在 O1 圆集合中选择一个元素,然后在 O2 圆集合中选择一个元素,使得这两个圆能将所有的点覆盖。
我们可以用 O(n2) 的复杂度来枚举两个圆,但是还要额外的复杂度来判断是否所有点在这两个圆内。这样总的复杂度是 O(n3)
考虑是否能让其中一个圆的集合有序,从而省略判断。于是我们将点集 P 按照点到 O1 的距离排序。这样当我们枚举到 pi 时,所有 pj(j<i) 都已经在圆 O1 中了。然后再从点 pk(k>i) 中找一个到 O2 有最大距离的点构成圆 O2 ,此时圆 O2 就包含了在圆 O1 之外的全部点。这样总的复杂度是 O(n2)
到此问题圆满解决了。但实际上还有复杂度更低的算法。我们可以将 r2(i)=max{O2k,k>i} 预处理出来,这样算上排序总的复杂度是 O(nlogn)

代码

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

typedef long long ll;

// 点结构体
struct Point {
    ll x, y;
    Point() {}
    Point(ll x, ll y): x(x), y(y) {}
    void input() {
        scanf("%I64d%I64d", &x, &y);
    }
    ll sqrDis(const Point& o) const {
        return (x - o.x) * (x - o.x) + (y - o.y) * (y - o.y);
    }
};

const int maxn = 2010;
int n;
Point f1, f2, a[maxn];
ll ans, Max[maxn];

// 排序用的比较函数
bool cmp(Point a, Point b) {
    return f1.sqrDis(a) < f1.sqrDis(b);
}

int main() {
    scanf("%d", &n);
    f1.input();
    f2.input();
    for(int i = 1; i <= n; i++) {
        a[i].input();
    }
    sort(a + 1, a + n + 1, cmp);
    Max[n+1] = 0;
    // 预处理出同O2距离最远的点
    for(int i = n; i >= 1; i--) {
        Max[i] = max(f2.sqrDis(a[i]), Max[i+1]);
    }
    ans = Max[1];
    for(int i = 1; i <= n; i++) {
        ans = min(ans, f1.sqrDis(a[i]) + Max[i+1]);
    }
    printf("%I64d\n", ans);
    return 0;
}

D. Polyline(Codeforces 617D)

思路

用笔在草稿纸上尝试后可以合理地提出假设,答案只有 123 三种。那么将输入根据三点的相对位置进行分类即可。

  1. 若三点有某个坐标分量两两相等,则可以用一个线段覆盖三个点。
  2. 若三点中有且仅有两个点 p1,p2 有某个坐标分量相等(不妨表示为 x1=x2 )。且另一个分量满足 yp3 不在 yp1 yp2 之间,则可以用两个线段覆盖三个点。
  3. 但以上两个条件都不满足的话,就只能用三个线段来覆盖三个点。

代码

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

int x[5], y[5];

int solve() {
    // 第一种情况
    if(x[1] == x[2] && x[2] == x[3]) {
        return 1;
    }
    if(y[1] == y[2] && y[2] == y[3]) {
        return 1;
    }
    int a, b, c;
    // 枚举上述p1,p2点,判断第二种情况
    for(int i = 1; i <= 3; i++) {
        for(int j = 1; j <= 3; j++) {
            for(int k = 1; k <= 3; k++) {
                if(i == j || j == k || i == k) {
                    continue;
                }
                if(x[i] == x[j]) {
                    a = min(y[i], y[j]);
                    b = max(y[i], y[j]);
                    c = y[k];
                    if(c <= a || c >= b) {
                        return 2;
                    }
                }
                if(y[i] == y[j]) {
                    a = min(x[i], x[j]);
                    b = max(x[i], x[j]);
                    c = x[k];
                    if(c <= a || c >= b) {
                        return 2;
                    }
                }
            }
        }
    }
    // 否则是第三种情况
    return 3;
}

int main() {
    for(int i = 1; i <= 3; i++) {
        cin >> x[i] >> y[i];
    }
    cout << solve() << endl;
    return 0;
}

E. XOR and Favorite Number(Codeforces 617E)

思路

如果对区间和比较敏感的话,求区间的异或和可以先对原数组做预处理,然后就能做到常数时间查询。因而我们令 b[x]=a[1]a[2]...a[x] 。于是区间 [x,y] 的异或和就能用 b[y]b[x1] 查询了。
即使做了优化,还是很难用合适的时间复杂度做查询。于是我们考虑通过小区间的答案推大区间的答案(或者相反)。假设我们已经知道 ans(x,y) (区间 [x,y] 的查询结果)了。现在想求 ans(x,y+1) ,如果我们要想知道 y+1 位置的元素对答案的贡献的话,就必须要知道在区间 [x,y] 中有多少前缀和 b[y]k 。为什么呢?因为 b[y]a[x]=k 等价于 b[y]k=a[x] 。于是我们可以用 sum[val] 来维护在当前区间,各个前缀异或和 val 出现的次数。从而 ans(x,y+1)=ans(x,y)+sum[b[y]k]
建立了 ans(x,y) ans(x,y+1) 的关系,就不难建立 ans(x,y) ans(x,y1) , ans(x1,y) ans(x+1,y) 的关系。从而满足了用莫队算法解决区间问题的条件。对查询二元组 (l,r) 按照分块思想排序后,这个问题就能用暴力移动双指针法在 O(nm) 的复杂度内解决了。
另外要注意几个易错点:

  1. sum 数组的大小至少要开成数组中的数的2倍(因为取异或值后可能比原数大)。
  2. insert erase 函数中两条语句的顺序不能变(因为统计的时候都是不计 idx 这个位置的元素的)。
  3. 所有的查询区间在输入后 l 要自减 1 (当然也可以在后面的操作中减)。
  4. insert(0) 这条语句不可删去(例如 k=1a[1]=1 且第一个查询是 [1,1] 时,如果不加这句的话查询结果就是 0 <script type="math/tex" id="MathJax-Element-120">0</script> )。

代码

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

typedef long long ll;
const int maxn = 1e5 + 10, maxa = 1e6 + 10;
int n, m, k, size;

// 排序用的查询结构体
struct query {
    int l, r, idx;
    query() {}
    query(int l, int r, int idx): l(l), r(r), idx(idx) {}
    int block() const {
        return l / size;
    }
    bool operator < (const query& o) const {
        return block() == o.block() ? r < o.r : block() < o.block();
    }
};

// 莫队算法
struct M {
    ll cnt;
    int a[maxn], sum[maxa<<1];
    ll ans[maxn];
    query q[maxn];
    // 输入数据
    void input(int n) {
        for(int i = 1; i <= n; i++) {
            scanf("%d", &a[i]);
        }
        for(int i = 2; i <= n; i++) {
            a[i] ^= a[i-1];
        }
    }
    // 向当前区间中加入新元素
    void insert(int idx) {
        cnt += sum[a[idx]^k];
        sum[a[idx]]++;
    }
    // 想当前区间中擦除边界元素
    void erase(int idx) {
        sum[a[idx]]--;
        cnt -= sum[a[idx]^k];
    }
    void solve(int m) {
        int l, r;
        for(int i = 1; i <= m; i++) {
            scanf("%d%d", &l, &r);
            q[i] = query(--l, r, i);
        }
        // 排序
        sort(q + 1, q + m + 1);
        insert(0);
        // 暴力移动指针
        int L = 0, R = 0;
        for(int i = 1; i <= m; i++) {
            l = q[i].l;
            r = q[i].r;
            for(; R < r; insert(++R));
            for(; L > l; insert(--L));
            for(; R > r; erase(R--));
            for(; L < l; erase(L++));
            ans[q[i].idx] = cnt;
        }
    }
    // 输出答案
    void output(int m) {
        for(int i = 1; i <= m; i++) {
            printf("%I64d\n", ans[i]);
        }
    }
}o;

int main() {
    scanf("%d%d%d", &n, &m, &k);
    size = sqrt(m);
    o.input(n);
    o.solve(m);
    o.output(m);
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值