3

我正在做一个需要二进制断层扫描算法的测试。提供了一组 38 个测试值来测试正确性,但完成所有测试也有 1 个 CPU 秒的时间限制。问题如下:

如果存在 m×n 矩阵 A,则输出“Yes”,每个元素为 0 或 1,使得

∑j=1nAi,j=ri∀i∈{1,2,…,m} 且 ∑i=1mAi,j=cj∀j∈{1,2,…,n}。

否则输出“否”。

对于每个测试,提供 2 个数组:

  1. r(矩阵中每一行的总和)
  2. c(矩阵中每一列的总和)

在等式中:

  • mr数组的长度,其中1 <= m
  • nc数组的长度,其中n <= 1000
  • rir的一个元素,其中0 <= ri <= n
  • cjc的一个元素,其中0 <= cj <= m

一个“是”的例子

米 = 3; n = 4; r = [2, 3, 2]; c = [1, 1, 3, 2];

在此处输入图像描述

一个“不”的例子

米 = 3; n = 3; r = [0, 0, 3]; c = [0, 0, 3];

我有一个似乎可以给出正确答案的解决方案,但是在超过 1 秒的 CPU 时间之前它只管理 12 / 38 次测试。

我最初在 ES5 中编写代码,然后返回并转换为 ES3 以尝试从中获得更多性能。(最初作为 ES5 管理 9 个测试)。我可以对当前算法做很多事情来提高性能(除非我弄错了)。这使我相信我的算法有问题,并且必须有更快的算法来执行此操作。我读了很多书试图找到一个,结果头疼:)

所以我转向社区,看看是否有人可以提出比我目前使用的更快的算法。

'use strict';

const ZEROS = (function (seed) {
  let string = seed;
  for (let i = 0; i < 19; i += 1) {
    string += seed;
  }

  return string;
}('00000000000000000000000000000000000000000000000000'));

const ZEROSLEN = ZEROS.length;

const permutate = function (n, ri) {
  const result = [];
  const memoize = {};
  let count = 0;
  do {
    const bin = count.toString(2);
    if (ZEROSLEN + bin.length > ZEROSLEN + n) {
      break;
    }

    if (!memoize[bin] && (bin.split('1').length - 1) === ri) {
      const string = (ZEROS + bin).slice(-n);
      const sLen = string.length;
      const perm = new Array(sLen);
      for (let i = sLen - 1; i >= 0; i -= 1) {
        perm[i] = +string[i];
      }

      memoize[bin] = result.push(perm);
    }

    count += 1;
  } while (count);

  return result;
};

const getMatrixSum = function (n, matrix) {
  const mLength = matrix.length;
  const rows = new Array(mLength);
  const a = new Array(n);
  const last = mLength - 1;
  for (let x = n - 1; x >= 0; x -= 1) {
    for (let y = last; y >= 0; y -= 1) {
      rows[y] = matrix[y][x];
    }

    let sum = 0;
    for (let i = rows.length - 1; i >= 0; i -= 1) {
      sum += rows[i];
    }

    a[x] = sum;
  }

  return a;
};

const isEqual = function (a, b) {
  const length = a.length;
  if (length !== b.length) {
    return false;
  }

  for (let i = length - 1; i >= 0; i -= 1) {
    if (a[i] !== b[i]) {
      return false;
    }
  }

  return true;
};

const addRow = function (i, prev, r, c, result) {
  if (result) {
    return result;
  }

  const n = c.length;
  const ri = r[i];
  if (ri < 0 || ri > n) {
    throw new RangeError('ri out of range');
  }

  const p = permutate(n, ri);
  const m = r.length;
  const rsLast = m - 1;
  const nextI = i + 1;
  for (let x = p.length - 1; x >= 0; x -= 1) {
    const permutation = p[x];
    const next = prev.slice();
    next.push(permutation);
    const sums = getMatrixSum(n, next);
    if (i < rsLast) {
      let memo = 0;
      for (let j = sums.length - 1; j >= 0; j -= 1) {
        if (sums[j] > c[j]) {
          memo += 1;
        }
      }

      if (!memo && addRow(nextI, next, r, c, result)) {
        return true;
      }
    } else if (isEqual(sums, c)) {
      return true;
    }
  }

  return false;
};

const isSolvable = function (r, c) {
  const m = r.length;
  const n = c.length;
  if (m < 1 || n > 1000) {
    throw new Error('Bad data');
  }

  for (let j = n; j >= 0; j -= 1) {
    const cj = c[j];
    if (cj < 0 || cj > m) {
      throw new RangeError('cj out of range');
    }
  }
  
  return addRow(0, [], r, c, false) ? 'Yes' : 'No';
};

console.log(isSolvable([2, 3, 2], [1, 1, 3, 2]));

console.log(isSolvable([0, 0, 3], [0, 0, 3]));

值得注意的是,测试是在 SpiderMonkey 版本 JavaScript-C24.2.0 上运行的

参考:

https://en.wikipedia.org/wiki/Discrete_tomography

https://open.kattis.com/problems/tomography

4

2 回答 2

2

Since permutations yield to brute force, they should be the last resort when developing algorithms similar to this one. Most of the time they are not needed.

As i have commented above, I have a feeling that one strategy could be first sorting the r and c arrays descending and start with the bigger ones. I haven't had time to implemented a JS code to work out this, so I haven't had a chance to test thoroughly. Please have a look and if you discover a flaw please mention.

In the below visual representation of the algorithm we try r = [1,3,1,3] and c = [3,2,1,2]. X denotes an occupied cell and a red dot denotes an untouchable cell while the empty ones are obviously the free cells. So in the real algorithm to represent a cell we need a data type like {value: false, avail: false} for a red dot while {value: false, avail: true} would mean a free space. Or to save space and speed you may use a data type like 0b00 for red dot, 0b01 for free space and 0b1X for occupied (X here means don't care) cells.

enter image description here

Note: It's worth mentioning Step 3 where we process c[0]. After we insert the three Xs we have to check the rows occupied by the Xs to update the status of the empty cells in those rows. In this case for r[2], all empty cells become untouchable.

Edit:

Well.. OK since we don't need to construct the solution in a 2D array like structure but only need an answer on wheather the supplied data is meaningful or not, I have come up with another and simpler idea which is essentially based on the above approach. I really don't think it can get any faster than this. It solves a 999 by 1000 board in like 50ms.

Lets get into it.

  1. The input is r = [2, 3, 2]; c = [1, 1, 3, 2]; However one important condition here is both c and r arrays should sum up to the same number. We can simply check this at the beginning of our code or leave it, go through the following steps and if they pass check only if c is full of 0s. The following code prefers the latter approach.
  2. Sort r descending so; r = [3, 2, 2]; c = [1, 1, 3, 2];
  3. Try reducing r[0] (3 in the first case) many non-zero elements of c by 1. Now c becomes [0, 0, 2, 2]. If it fails then try no more and return false.
  4. Now that we have finished with row r[0], recursivelly call function with r = [2, 2]; c = [0, 0, 2, 2]; while r.length is bigger than 0 and the bool argument b is true. Next call will be r = [2]; c = [0, 0, 1, 1]; and finally r = []; c = [0, 0, 0, 0];
  5. If finally a recursive call with empty r is invoked then check b is true and all items of c are 0. (b && cs.every(n => !n)).

I believe this is just fine but as i don't have your test cases it's for you to try. I am sure it will pass the time test though. Here is the code in it's simplest. Here i am testing rs = [7,3,5,4,6,2,8] and cs = [7,1,6,3,4,5,2,7]. It looks like;

  71634527
7 x xxxxxx
3 x x    x
5 x x xx x
4 x x  x x
6 x xxxx x
2 x      x
8 xxxxxxxx

function nonogram(rs,cs){
  function runner(rs,cs, b = true){//console.log(rs,cs,b)
    return b && rs.length ? runner(rs.slice(1),                                 // rows argument
                                   cs.map(e => rs[0] ? e ? (b = !--rs[0], e-1)  // cols argument
                                                         : e
                                                     : e),
                                   b)                                           // bool argument
                          : b && cs.every(n => !n);
  }
return runner(rs.sort((a,b) => b-a), cs);
}

var rs = [7,3,5,4,6,2,8],
    cs = [7,1,6,3,4,5,2,7],
    result;
console.time("test");
result = nonogram(rs,cs);
console.timeEnd("test");
console.log(result);

于 2017-10-06T12:55:42.047 回答
0

我还没有准备好进行测试,但是在事件发生后我发现了一个更有效的算法。

'use strict';

const sortNumber = function (a, b) {
  return b - a;
};

const isSolvable = function (r, c) {
  const m = r.length;
  const n = c.length;
  if (m < 1 || n > 1000) {
    throw new Error('Bad data');
  }

  for (let j = n; j >= 0; j -= 1) {
    const cj = c[j];
    if (cj < 0 || cj > m) {
      throw new RangeError('cj out of range');
    }
  }

  while (r.length) {
    c.sort(sortNumber);
    const ri = r.pop();
    if (ri < 0 || ri > n) {
      throw new RangeError('ri out of range');
    }

    if (ri) {
      if (!c[ri - 1]) {
        return 'No';
      }

      for (let j = ri - 1; j >= 0; j -= 1) {
        c[j] -= 1;
      }
    }
  }

  for (let j = n - 1; j >= 0; j -= 1) {
    if (c[j]) {
      return 'No';
    }
  }

  return 'Yes';
};

console.log(isSolvable([2, 3, 2], [1, 1, 3, 2]));

console.log(isSolvable([0, 0, 3], [0, 0, 3]));

于 2017-10-06T10:50:37.080 回答