【问题标题】:Difficulty understanding someones source code to a solution of an IOI problem难以理解某人的源代码来解决 IOI 问题
【发布时间】:2020-07-10 16:48:36
【问题描述】:

这里是问题的链接:https://ioi2019.az/source/Tasks/Day1/Shoes/NGA.pdf

以下是关于问题说明的简要说明:

给你一个整数 n1≤n≤1e5 范围内,它表示数组中正整数的数量,以及数组中负整数的数量(因此数组的总大小将是 2n)。

问题要你找到数组中需要的最小交换次数,使得一个数的负值和该负数的绝对值彼此相邻(这样 -x 是x) 的右侧

例子:

n = 2;

输入的数组 = {2, 1, -1, -2}

最少操作数为四次:

2,1,-1,-2:0 次交换

2,-1,1,-2: 1 次交换(交换 1 和 -1)

2,-1,-2,1:2 次交换(交换 1 和 -2)

2,-2,-1,1:3 次交换(交换 -1 和 -2)

-2,2,-1,1:4 次交换(交换 2 和 -2)

最后的答案是四个。

另一个例子:

输入的数组 = {-2, 2, 2, -2, -2, 2}

最小交换为 1。因为我们可以交换索引 2 和 3 的元素。

最终数组:{-2,2,-2,2,-2,2}


在做这个问题时,我得到了错误的答案,我决定在 git hub 上查看某人的源代码。

这里是源代码:

#include "shoes.h"
#include <bits/stdc++.h>
#define sz(v) ((int)(v).size())
using namespace std;
using lint = long long;
using pi = pair<int, int>;
const int MAXN = 200005;

struct bit{
    int tree[MAXN];
    void add(int x, int v){
        for(int i=x; i<MAXN; i+=i&-i) tree[i] += v;
    }
    int query(int x){
        int ret = 0;
        for(int i=x; i; i-=i&-i) ret += tree[i];
        return ret;
    }
}bit;


lint count_swaps(vector<int> s) {
    int n = sz(s) / 2;
    lint ret = 0;
    vector<pi> v;
    vector<pi> ord[MAXN];
    for(int i=0; i<sz(s); i++){
        ord[abs(s[i])].emplace_back(s[i], i);
    }
    for(int i=1; i<=n; i++){
        sort(ord[i].begin(), ord[i].end());
        for(int j=0; j<sz(ord[i])/2; j++){
            int l = ord[i][j].second;
            int r = ord[i][j + sz(ord[i])/2].second; //confusion starts here all the way to the buttom
            if(l > r){
                swap(l, r);
                ret++;
            }
            v.emplace_back(l + 1, r + 1);
        }
    }
    for(int i=1; i<=2*n; i++) bit.add(i, 1);
    sort(v.begin(), v.end());
    for(auto &i : v){
        ret += bit.query(i.second - 1) - bit.query(i.first);
        bit.add(i.first, -1);
        bit.add(i.second, -1);
    }
    return ret;
}

但是,我觉得我不太了解这段代码。

我了解 BIT 中的 addquery 函数是做什么的,我只是对我一直到底部对代码的评论位置感到困惑。我不明白它的作用和目的是什么。

有人可以看看这段代码在做什么吗?或者就我应该如何正确有效地解决这个问题提供任何建议(甚至可能是你的解决方案?)。谢谢。

【问题讨论】:

  • #define sz(v) ((int)(v).size()) using namespace std; 等。为什么这些疯狂的宏?这无疑来自那些“竞争性编程”网站之一。
  • @Rainier1 Cargo-cult programming 应该避免。此外,该解决方案可以使用如下声明来炸毁堆栈内存:vector&lt;pi&gt; ord[MAXN];。没有一个现实世界的程序员会盲目地声明 200000+ std::vector's。
  • 无论你从哪里获得源代码,请不要回到那里。来源不好,不值得尝试从中学习任何东西(除非你想看到很多坏习惯)。
  • 哇!从该代码中可以学到很多东西,其中充满了“如何不做 C++”。
  • #include &lt;bits/stdc++.h&gt; - 不,不,!不要永远那样做。

标签: c++ algorithm std-pair


【解决方案1】:
int r = ord[i][j + sz(ord[i])/2].second;

我们已经在&lt;size, idx&gt; 的向量中对一个鞋码的元组进行了排序,这意味着这个尺寸的所有负数都占据了ord[i] 的前半部分,所有正数都在后半部分。

if (l > r){
  swap(l, r);
  ret++;
}

在我们对大小进行排序之后,每个对应对的索引可能不会以负数在正数之前排序。每一个都需要交换。

v.emplace_back(l + 1, r + 1);

v我们的区间中插入对应尺码i的鞋子。

for(int i=1; i<=2*n; i++) bit.add(i, 1);
sort(v.begin(), v.end());

在我们的段和树中为鞋子的每个索引位置添加值 1。对鞋间隔进行排序。

for(auto &i : v){
  ret += bit.query(i.second - 1) - bit.query(i.first);

对于v 中的每双鞋,需要的交换次数是它们之间剩余的鞋子数量,以分段总和表示。

bit.add(i.first, -1);
bit.add(i.second, -1);

从树中移除这双鞋,这样新的段总和就不会包括它们。我们可以这样做,因为鞋间隔是从左到右处理的,这意味着没有“内”鞋在外鞋之前被处理。

【讨论】:

  • 感谢您的解释。我现在了解大多数事情,但我很困惑为什么我们添加 l+1 和 r+1 而不是 l 和 r,以及为什么我们从一个中减去 i.second 而不是 i.first。除此之外,非常感谢您的解释。
  • @Rainier1 树使用非从零开始的索引。第一个索引是 1,所以我们将 1 添加到 lr 到偏移量,以便它们在树查找中工作。我认为您的第二个问题是关于bit.query(i.second - 1)。我不是 100% 确定,但我相信这是对查询间隔的另一个更正。 bit.query 是前缀和。如果我们有 [1, 1, 1, 1] 值,对 4 的查询将返回 4,对 1 的查询将返回 1。但是 4 - 1 = 3 是不正确的。我们想要 2,即 1 和 4 之间的 1 的数量。所以我们从 3 开始:3 - 1 = 2。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-03-01
  • 1970-01-01
  • 2011-03-28
  • 1970-01-01
相关资源
最近更新 更多