【问题标题】:Sum of two 'rare' matrices, read as coordinate X, coordinate Y and a value两个“稀有”矩阵的总和,读作坐标 X、坐标 Y 和一个值
【发布时间】:2019-07-24 11:43:52
【问题描述】:

当我在 pbinfo.ro 上处理 structs 的问题时,我发现了一个问题,其中我得到了两个矩阵的维度,每个矩阵的 nonzero 元素的数量(N1,分别为 N2)然后 N1 + N2 组 3 个数字如下:

  • 坐标,X 和 Y
  • 位于这些坐标处的值

我要做的是编写一个程序,对这两个矩阵求和(它们都具有相同的维度)并输出输入中给出的和(X、Y 和坐标处的值)。

问题全文如下:

Matrice-Rara | www.pbinfo.ro

要求

给出了两个稀有矩阵,你 被要求计算它们的总和。

如果矩阵A (n, m) 的大部分元素等于 零(至少一半)。由于非零数的数量很少,一个 稀有矩阵A (n, m)k 非零元素可以使用 数组X 包含k 三元组形式(行、列、值)对应于 矩阵的非零值。数组X 的元素存储在 linecolumn 的字典顺序。

例如带有n = m = 3的矩阵:

1 0 2
0 0 5
0 2 0

将以这种方式保存在X{(1,1,1), (1,3,2), (2,3,5), (3,2,2)}

输入数据

输入文件matrice_rara.in 在第一行包含 两个矩阵的维度 n m,代表行数 和列,以及N1 N2,矩阵A 的非零元素的数量和 矩阵B。然后下面的N1 行将包含三元组 矩阵A 按字典顺序排列,最后一行N2 将包含 表示矩阵B 的非零元素的三元组,也在 字典顺序。

输出数据

输出文件matrice_rara.out 将在第一行包含 矩阵C 中非零元素的数量,然后是矩阵 本身以按字典顺序排列的三元组形式,每行一个。

限制和说明

  • 1 ≤ n, m ≤ 1,000,000
  • 1 ≤ N1, N2 ≤ 300,000
  • -1,000,000,000 ≤ A[i][j], B[i][j] ≤ 1,000,000,000
  • Time Limit: 1 second
  • Memory Limit: 64 MB / 8 MB

我尝试的是读取一个三元组数组中的所有三元组,对数组进行排序,然后将具有相同坐标的元素的值添加到数组中具有重复坐标的第一个元素中。

这是我的代码:

#include <fstream>
#include <algorithm>

using namespace std;

ifstream cin("matrice_rara.in");
ofstream cout("matrice_rara.out");

struct matrix
{
    int coord1, coord2;
    long long val;
} data[600001];

bool operator<(matrix const &a, matrix const &b)
{
    if (a.coord1 == b.coord1) {
        if (a.coord2 == b.coord2) {
            return a.val < b.val;
        }
        return a.coord2 < b.coord2;
    }
    return a.coord1 < b.coord1;
}

int main()
{
    int lin, col, unNul1, unNul2;
    cin >> lin >> col >> unNul1 >> unNul2;

    for (int i = 1; i <= unNul1 + unNul2; i++)
        cin >> data[i].coord1 >> data[i].coord2 >> data[i].val;

    sort(data + 1, data + unNul1 + unNul2 + 1);

    int unNull = 0;
    for (int i = 1; i <= unNul1 + unNul2; i++)
    {
        int start = i;
        while (data[i + 1].coord1 == data[i].coord1 && data[i + 1].coord2 == data[i].coord2 && i + 1 <= unNul1 + unNul2)
            i++, data[start].val += data[i].val;
        if (data[start].val)
            unNull++;
    }
    cout << unNull << "\n";
    for (int i = 1; i <= unNul1 + unNul2; i++)
    {
        if (data[i].val)
            cout << data[i].coord1 << " " << data[i].coord2 << " " << data[i].val << "\n";
        while (data[i + 1].coord1 == data[i].coord1 && data[i + 1].coord2 == data[i].coord2 && i + 1 <= unNul1 + unNul2)
            i++;
    }
}

上面的代码得到 65 分(满分 100 分),9 次测试中有 6 次得到正确答案,其余的都得到错误答案(没有 TLE)。

有什么帮助吗?

【问题讨论】:

  • 请不要将在线评委或竞赛网站作为基本的学习资源,它们不会真正教给你任何有用的东西,除了擅长这些网站之外,没有太多其他的东西。相反,我建议您 get a few good books 阅读。如果您切换到更好的学习资源,您将不会(希望)养成使用基于 1 的数组索引(除其他外)这样的坏习惯。
  • 作为您的问题可能的一个可能提示:您有一个错误,您超出范围data 数组中的初始化数据。如果你正确地学习了 C++,那么你可能不会出现这个错误。
  • @Someprogrammerdude 我确实读过一些书,但它们只是让我开始编程。此外,那些教我这种基于一的索引编程的人。从那时起(5 年前),我就一直以这种方式编程。我尽我所能摆脱这种习惯,然而,这并不容易,因为现在感觉像是本能的东西。在线评委不是我的学习资源,别担心。我只使用它们来锻炼我编写正确、干净代码的能力。另外,600001 足以确保元素适合,否则在线法官会给我Caught Fatal Signal 11
  • 在这种情况下,我很抱歉,但仍然恳请您购买我链接到的(策划)列表中列出的本书之一。它还应该有望解释为什么放 ` && i + 1 short-circuit evaluation)。
  • 顺便说一句,那些是sparse matrices

标签: c++ arrays sorting matrix struct


【解决方案1】:

如果您有逻辑 AND 运算符 &amp;&amp;,并且左侧表达式为 false,则不计算右侧表达式。这称为short-circuit evaluation

这也意味着必须先评估左侧,然后代码才知道是否需要评估右侧。

现在让我们从您的代码中提取一个条件表达式,并稍微简化一下:

data[i + 1].coord1 == data[i].coord1 && i + 1 <= unNul1 + unNul2

这是结合&amp;&amp; 运算符的两个子条件:

  1. 左侧data[i + 1].coord1 == data[i].coord1,和
  2. 右侧i + 1 &lt;= unNul1 + unNul2

由于&amp;&amp; 运算符的短路特性,必须先评估左侧 (data[i + 1].coord1 == data[i].coord1),然后再评估右侧 (i + 1 &lt;= unNul1 + unNul2)。

现在,如果 i 恰好等于 unNul1 + unNul2,那么左侧表达式将使用超出 1 的索引。

为了不越界,你需要检查索引ifirst,最简单的方法是将条件的顺序切换到

i + 1 <= unNul1 + unNul2 && data[i + 1].coord1 == data[i].coord1

现在i + 1 不会越界了。


至于为什么重要是因为未初始化的数据会有indeterminate 值,并且以几乎所有方式使用它们都会导致undefined behavior。使用当前的循环条件,您使用未初始化的数组data 部分的索引。

【讨论】:

  • 刚刚提交了i + 1 &lt;= unNul1 + unNul2的解决方案。同样的结果,同样的测试失败。还有其他建议吗?
【解决方案2】:

如果我理解了发布代码的意图,OP 会尝试实施此算法来对两个稀疏矩阵求和:

  • 两个矩阵的非零元素读入一个足够大的数组,以存储问题约束允许的最大元素数。请注意,输入格式正确,三元组代表 n.z。这两个矩阵的元素已经按字典顺序排序(行优先)。
  • 按字典顺序对整个数组进行排序。请注意,在比较函数中,n.z 的 value。元素也被考虑在内。
  • 遍历数组,如果两个连续元素的坐标相同,则将它们的值相加,并将结果存储在第一个元素中。如果结果值非零,则 n.z. 的计数。相应地更新。这里已经指出了越界访问。
  • 再次遍历数组以打印出结果矩阵的非零值,跳过具有相同坐标的连续元素的值。与以前一样的越界访问。

除了访问越界导致的 UB 之外,我不清楚接受的解决方案是否需要实际上检查每个元素的结果值是否非零。

即使是这样,我认为我们可以利用已经排序的输入,并通过“合并”两个源来增量创建结果矩阵。在下面的 sn-p (testable here) 中,我还简化了两个元素之间的比较,使用一个表示位置的 64 位值而不是两个 32 位坐标。

#include <iostream>
#include <vector>
#include <cstdint>
#include <cassert>
#include <utility>
#include <iterator>

constexpr uint64_t pos_from(uint32_t r, uint32_t c)
{
    return (static_cast<uint64_t>(r) << 32) ^ c;
}

constexpr uint32_t row_from(uint64_t pos)
{
    return (pos >> 32) & 0xFFFFFFFF;
}

constexpr uint32_t col_from(uint64_t pos)
{
    return pos & 0xFFFFFFFF;
}

template <typename T>
class SparseMatrix
{
    uint32_t rows_{}, cols_{};
    std::vector<std::pair<uint64_t, T>> nzs_;
public:
    SparseMatrix(uint32_t r, uint32_t c, uint32_t nzs = 0)
        : rows_{r}, cols_{c}
    {
        nzs_.reserve(nzs);
    }

    std::istream& read_non_zeroes_from(std::istream& is, uint32_t n)
    {
        while(n--)
        {
            uint32_t r, c;
            T value;
            if ( (is >> r >> c >> value)  &&  value)            
                nzs_.emplace_back(pos_from(r, c), value);
        }
        return is;
    }

    std::ostream& write_non_zeroes_to(std::ostream& os)
    {
        os << nzs_.size() << '\n';
        for (auto const& i : nzs_)
            os << row_from(i.first) << ' ' << col_from(i.first) << ' ' << i.second << '\n';
        return os;
    }

    friend SparseMatrix operator+ (SparseMatrix const& a, SparseMatrix const& b)
    {
        assert(a.rows_ == b.rows_  &&  a.cols_ == b.cols_);
        SparseMatrix sum(a.rows_, a.cols_, std::max(a.nzs_.size(), b.nzs_.size()));

        auto i = a.nzs_.cbegin();
        auto j = b.nzs_.cbegin();
        auto dest = std::back_inserter(sum.nzs_);
        while ( i != a.nzs_.cend()  &&  j != b.nzs_.cend() )
        {
            if ( i->first < j->first )
            {
                *dest = *i;
                ++i;
            }
            else if ( j->first < i->first )
            {
                *dest = *j;
                ++j;
            }
            else
            {
                auto sum_value = i->second + j->second;
                if (sum_value)
                    *dest = {i->first, sum_value};
                ++i;
                ++j;   
            }
        }
        std::copy(i, a.nzs_.cend(), dest);
        std::copy(j, b.nzs_.cend(), dest);
        return sum;
    }
};

int main()
{
    uint32_t n, m, N1, N2;
    if ( !(std::cin >> n >> m >> N1 >> N2) )
        std::cerr << "Error: unable to read initial data.\n";

    SparseMatrix<int32_t> a(n, m, N1);
    if ( !a.read_non_zeroes_from(std::cin, N1) )
        std::cerr << "Error: unable to read triplets of the first matrix\n";

    SparseMatrix<int32_t> b(n, m, N2);
    if ( !b.read_non_zeroes_from(std::cin, N2) )
        std::cerr << "Error: unable to read triplets of the second matrix.\n";

    auto c = a + b;
    c.write_non_zeroes_to(std::cout);
}

【讨论】:

  • 好吧,因为我不太了解你的代码,所以我决定看看在线法官会如何反应。你猜怎么了?显然,这也得到了 65 分。另外,是的,如果两个元素的结果值为零,则必须从输出中省略最后一个元素。在这种情况下,我很想看到官方测试,但我们不能。
【解决方案3】:

嗯,我刚刚设法达到 100 分。基本上,我的错误是我定义operator&lt; 的方式,或者在我在这里发布的源代码中(从那以后我重写了几次),我写了一个if 声明错误的事实,而不是while 一个。这是新的来源:

#include <fstream>

using namespace std;

ifstream cin("matrice_rara.in");
ofstream cout("matrice_rara.out");

struct matrixEntry {
    int l, c;
    int val;
} matrix1[300001], matrix2[300001], finalMatrix[300001];

bool operator< (const matrixEntry &a, const matrixEntry &b)
{
    return a.l < b.l || (a.l == b.l && a.c < b.c);
}

bool operator> (const matrixEntry &a, const matrixEntry &b)
{
    return a.l > b.l || (a.l == b.l && a.c > b.c);
}

int matrixSum(int matrix1Size, int matrix2Size)
{
    int i = 1, j = 1, k = 0;
    while (i <= matrix1Size && j <= matrix2Size)
    {
        if (matrix1[i] < matrix2[j])
            finalMatrix[++k] = matrix1[i++];
        else if (matrix1[i] > matrix2[j])
            finalMatrix[++k] = matrix2[j++];
        else
        {
            finalMatrix[++k] = matrix1[i++];
            finalMatrix[k].val += matrix2[j++].val;
            if (finalMatrix[k].val == 0)
                k--;
        }
    }
    while (i <= matrix1Size)
        finalMatrix[++k] = matrix1[i++];
    while (j <= matrix2Size)
        finalMatrix[++k] = matrix2[j++];
    return k;
}

int main()
{
    int unNul1, unNul2, lin, col;

    cin >> lin >> col >> unNul1 >> unNul2;

    for (int i = 1; i <= unNul1; i++)
        cin >> matrix1[i].l >> matrix1[i].c >> matrix1[i].val;
    for (int i = 1; i <= unNul2; i++)
        cin >> matrix2[i].l >> matrix2[i].c >> matrix2[i].val;

    int unNull = matrixSum(unNul1, unNul2);
    cout << unNull << "\n";
    for (int i = 1; i <= unNull; i++)
    {
        cout << finalMatrix[i].l << " " << finalMatrix[i].c << " " << finalMatrix[i].val << "\n";
    }
}

【讨论】:

  • 我也很想看看官方测试。请注意,该程序最多为最终矩阵分配 300001 个条目,考虑到约束,这可能还不够。另外,尝试仅实现operator&lt;,并使用... else if (matrix2[j] &lt; matrix1[i]) ...。应该没有什么区别。
  • @Bob__ 是的,没错,本来可以这样做的…… 4 行毫无意义。但无论如何,300001 限制很好,因为它是从 1 索引的,这意味着它需要比从 0 索引的数组多一个条目。
  • 不,那是“好的”,我指的是 1 ≤ N1,N2 ≤ 300,000 约束。 N1 和 N2 都可以与最大值一样大,并且由此产生的非零数可以达到 N1 + N2。无论如何,如果这通过了所有测试,它永远不会发生。
  • @Bob__ 你想知道为什么从来没有会发生吗?因为你不能对两个不同大小的矩阵求和。而且,如果您将两个相同大小的矩阵相加,则结果不能是大小的两倍。 :)
  • 实际上,忽略这一点,测试应该已经超过了 300001 的限制,因为这不是矩阵的大小,而是给出的三元组的数量。
猜你喜欢
  • 1970-01-01
  • 2011-10-01
  • 1970-01-01
  • 2021-04-01
  • 2021-04-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多