Эффективный алгоритм для подсчета числа целых сеток
Рассмотрим квадрат 3 на 3 сетки неотрицательных целых чисел. Для каждой строки i
сумма целых чисел равна r_i
. Аналогично для каждого столбца j
сумма целых чисел в этом столбце равна c_j
. Поэтому экземпляр проблемы описывается 6
неотрицательными целыми числами.
Существует ли эффективный алгоритм для подсчета количества различных присваивания целых чисел в сетку даны строка и столбец сумма ограничений?
Очевидно, можно было бы перечислить все возможные матрицы неотрицательных целых чисел со значениями до sum r_i
и проверить ограничения для каждого, но это было бы безумно медленным.
Пример
Скажите, что ограничения строк 1 2 3
, а ограничения столбца 3 2 1
. Возможные целые сетки:
┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
│0 0 1│0 0 1│0 0 1│0 1 0│0 1 0│0 1 0│0 1 0│1 0 0│1 0 0│1 0 0│1 0 0│1 0 0│
│0 2 0│1 1 0│2 0 0│0 1 1│1 0 1│1 1 0│2 0 0│0 1 1│0 2 0│1 0 1│1 1 0│2 0 0│
│3 0 0│2 1 0│1 2 0│3 0 0│2 1 0│2 0 1│1 1 1│2 1 0│2 0 1│1 2 0│1 1 1│0 2 1│
└─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
На практике мой основной интерес заключается в том, что общая сумма сетки будет не более 100, но более общее решение было бы очень интересно.
Ответы
Ответ 1
Это не поможет с проблемой: # P-hard (если вы разрешаете матрице любых размеров - см. ссылку в комментарии ниже), но есть решение, которое не означает перечисление всех а скорее меньший набор объектов, называемых полустандартных молодых таблиц. В зависимости от вашего ввода, он может идти быстрее, но все еще имеет экспоненциальную сложность. Поскольку это целая глава в нескольких книгах по алгебраической комбинатонике или в Knuth AOCP 3, я не буду вдаваться в подробности здесь, только указывая на соответствующие страницы википедии.
Идея состоит в том, что с помощью соответствия Робинсона-Шенстеда-Кнута каждая из этих матриц в биекции с парой таблиц одинаковой формы, где одна из таблиц заполняется целыми числами, подсчитанными суммой строк, другая - суммой столбца. Число таблиц формы U, заполненных номерами, подсчитанными V, называется Kostka Number K (U, V). Как следствие, вы получаете формулу, например
#Mat(RowSum, ColSum) = \sum_shape K(shape, RowSum)*K(shape, ColSum)
Конечно, если RowSum == ColSum == Sum:
#Mat(Sum, Sum) = \sum_shape K(shape, Sum)^2
Вот ваш пример в системе SageMath:
sage: sum(SemistandardTableaux(p, [3,2,1]).cardinality()^2 for p in Partitions(6))
12
Вот несколько более крупных примеров:
sage: sums = [6,5,4,3,2,1]
sage: %time sum(SemistandardTableaux(p, sums).cardinality()^2 for p in Partitions(sum(sums)))
CPU times: user 228 ms, sys: 4.77 ms, total: 233 ms
Wall time: 224 ms
8264346
sage: sums = [7,6,5,4,3,2,1]
sage: %time sum(SemistandardTableaux(p, sums).cardinality()^2 for p in Partitions(sum(sums)))
CPU times: user 1.95 s, sys: 205 µs, total: 1.95 s
Wall time: 1.94 s
13150070522
sage: sums = [5,4,4,4,4,3,2,1]
sage: %time sum(SemistandardTableaux(p, sums).cardinality()^2 for p in Partitions(sum(sums)))
CPU times: user 1.62 s, sys: 221 µs, total: 1.62 s
Wall time: 1.61 s
1769107201498
Ясно, что вы не получите быстрых перечисляющих матриц.
В соответствии с запросом גלעד ברקן @здесь представлено решение с различными суммами строк и столбцов:
sage: rsums = [5,4,3,2,1]; colsums = [5,4,3,3]
sage: %time sum(SemistandardTableaux(p, rsums).cardinality() * SemistandardTableaux(p, colsums).cardinality() for p in Partitions(sum(rsums)))
CPU times: user 88.3 ms, sys: 8.04 ms, total: 96.3 ms
Wall time: 92.4 ms
10233
Ответ 2
Есть ли эффективный алгоритм для подсчета количества различных назначений целых чисел в сетке, которые заданы ограничениями строки и столбца?
upd Мой ответ неверен для этой конкретной проблемы, когда N
фиксирован (т.е. становится постоянным 3
). В этом случае он многочлен. Извините за вводящую в заблуждение информацию.
TL; DR: Я думаю, что это по крайней мере NP-hard. Полиномиального алгоритма нет, но, возможно, есть некоторые эвристические ускорения.
Для сетки N-by-N у вас есть уравнения N
для сумм строк, N
уравнения для сумм суммы и N^2
неотрицательные ограничения:
![введите описание изображения здесь]()
Для N > 2
эта система имеет более одного возможного решения в целом. Потому что есть N^2
неизвестные переменные x_ij
и просто 2N
equation = > для N > 2
: N^2 > 2N
.
Вы можете исключить переменные 2N - 1
, чтобы оставить только одно уравнение с переменными K = N^2 - (2N-1)
, получая сумму S
. Тогда вам придется иметь дело с проблемой целочисленного раздела, чтобы узнать все возможные комбинации терминов K
, чтобы получить S
. Эта проблема NP-полная. И количество комбинаций зависит не только от количества слагаемых K
, но и от порядка значения S
.
Эта проблема напомнила мне о Simplex method. Моя первая мысль заключалась в том, чтобы найти только одно решение, используя что-то вроде этого метода, а затем пересечь края выпуклого, чтобы найти все возможные решения. И я надеялся, что для этого есть оптимальный алгоритм. Но нет, целочисленный симплекс-метод, связанный с целочисленным линейным программированием, NP-hard: (
Надеюсь, есть какие-то эвристики для связанных проблем, которые вы можете использовать для ускорения наивного решения грубой силы.
Ответ 3
Я не знаю подходящего алгоритма, но я не думаю, что было бы трудно работать. Для любого решения вы можете получить другое решение, выбрав четыре угла прямоугольной области вашей сетки, увеличив два диагональных угла на какое-то значение и уменьшив остальные два на то же значение. Диапазон для этого значения будет ограничен самым низким значением каждой диагональной пары. Если вы определяете размер всех таких диапазонов, вы должны иметь возможность размножать их вместе, чтобы определить общие возможные решения.
Предполагая, что вы описали свою сетку как обычную таблицу в алфавитном порядке для столбцов и численно для строк, вы можете описать все возможные области в следующем списке:
A1:B2, A1:B3, A1:C2, A1:C3, B1:C2, B1:C3, A2:B3, A2:C3, B2:C3
Для каждой области мы приводим в таблицу диапазон, основанный на самом низком значении от каждой диагональной угловой пары. Вы можете постепенно уменьшать пару до тех пор, пока элемент не достигнет нуля, потому что нет верхней границы для другой пары.
Выбирая первое решение вашего примера, мы можем получить все другие возможные решения, используя эту технику.
A B C
┌─────┐
1 │0 0 1│ sum=1
2 │0 2 0│ sum=2
3 │3 0 0│ sum=3
└─────┘
3 2 1 = sums
A1:B2 - 1 solution (0,0,0,2)
A1:C2 - 1 solution (0,1,0,0)
A1:B3 1 solution (0,0,3,0)
A1:C3 2 solutions (0,1,3,0), (1,0,2,1)
B1:C2 2 solutions (0,1,2,0), (1,0,1,1)
B1:C3 1 solution (0,1,0,0)
A2:B3 3 solutions (0,2,3,0), (1,1,2,1), (2,0,1,2)
A2:C3 1 solution (0,0,3,0)
B2:C3 1 solution (2,0,0,0)
Умножьте все решения вместе, и вы получите 2 * 2 * 3 = 12 решений.
Ответ 4
Возможно, простое решение с 4-вложенными циклами достаточно быстро, если общая сумма мала?
function solve(rowsum, colsum) {
var count = 0;
for (var a = 0; a <= rowsum[0] && a <= colsum[0]; a++) {
for (var b = 0; b <= rowsum[0] - a && b <= colsum[1]; b++) {
var c = rowsum[0] - a - b;
for (var d = 0; d <= rowsum[1] && d <= colsum[0] - a; d++) {
var g = colsum[0] - a - d;
for (var e = 0; e <= rowsum[1] - d && e <= colsum[1] - b; e++) {
var f = rowsum[1] - d - e;
var h = colsum[1] - b - e;
var i = rowsum[2] - g - h;
if (i >= 0 && i == colsum[2] - c - f) ++count;
}
}
}
}
return count;
}
document.write(solve([1,2,3],[3,2,1]) + "<br>");
document.write(solve([22,33,44],[30,40,29]) + "<br>");
Ответ 5
Я устал оптимизировать медленный вариант. Я получаю все комбинации и изменяю код только для того, чтобы получить общее количество. Это самое быстрое, что я мог бы получить:
private static int count(int[] rowSums, int[] colSums)
{
int count = 0;
int[] row0 = new int[3];
int sum = rowSums[0];
for (int r0 = 0; r0 <= sum; r0++)
for (int r1 = 0, max1 = sum - r0; r1 <= max1; r1++)
{
row0[0] = r0;
row0[1] = r1;
row0[2] = sum - r0 - r1;
count += getCombinations(rowSums[1], row0, colSums);
}
return count;
}
private static int getCombinations(int sum, int[] row0, int[] colSums)
{
int count = 0;
int max1 = Math.Min(colSums[1] - row0[1], sum);
int max2 = Math.Min(colSums[2] - row0[2], sum);
for (int r0 = 0, max0 = Math.Min(colSums[0] - row0[0], sum); r0 <= max0; r0++)
for (int r1 = 0; r1 <= max1; r1++)
{
int r01 = r0 + r1;
if (r01 <= sum)
if ((r01 + max2) >= sum)
count++;
}
return count;
}
Stopwatch w2 = Stopwatch.StartNew();
int res = count(new int[] { 1, 2, 3 }, new int[] { 3, 2, 1 });//12
int res1 = count(new int[] { 22, 33, 44 }, new int[] { 30, 40, 29 });//117276
int res2 = count(new int[] { 98, 99, 100}, new int[] { 100, 99, 98});//12743775
int res3 = count(new int[] { 198, 199, 200 }, new int[] { 200, 199, 198 });//201975050
w2.Stop();
Console.WriteLine("w2:" + w2.ElapsedMilliseconds);//322 - 370 on my computer
Ответ 6
Помимо моего другого ответа с использованием биекции Робинсона-Шенстеда-Кнута, здесь
другое решение, которое не нуждается в передовой комбинаторике, но некоторый трюк в
программирование решает эту задачу для произвольной более крупной матрицы. Первая идея
который должен использоваться для решения таких проблем, заключается в использовании рекурсии, избегая перекомпоновки вещей благодаря некоторой мемуаризации
или улучшенное динамическое программирование. В частности, если вы выбрали кандидата
для первой строки вы вычитаете эту первую строку в сумму столбца, и вы
слева с той же проблемой только одна меньше строки. Чтобы избежать перекомпоновки
вещь, в которой вы сохраняете результат. Вы можете сделать это
-
либо в основном в большой таблице (memoization)
-
или более сложным способом, сохраняя все решения для матриц с n строками
и выводит число решений для матриц с n + 1 строками (динамическое программирование).
Вот рекурсивный метод, использующий memoization в Python:
# Generator for the rows of sum s which are smaller that maxrow
def choose_one_row(s, maxrow):
if not maxrow:
if s == 0: yield []
else: return
else:
for i in range(0, maxrow[0]+1):
for res in choose_one_row(s-i, maxrow[1:]):
yield [i]+res
memo = dict()
def nmat(rsum, colsum):
# sanity check: sum by row and column must match
if sum(rsum) != sum(colsum): return 0
# base case rsum is empty
if not rsum: return 1
# convert to immutable tuple for memoization
rsum = tuple(rsum)
colsum = tuple(colsum)
# try if allready computed
try:
return memo[rsum, colsum]
except KeyError:
pass
# apply the recursive formula
res = 0
for row in choose_one_row(rsum[0], colsum):
res += nmat(rsum[1:], tuple(a - b for a, b in zip(colsum, row)))
# memoize the result
memo[(tuple(rsum), tuple(colsum))] = res
return res
Затем после этого:
sage: nmat([3,2,1], [3,2,1])
12
sage: %time nmat([6,5,4,3,2,1], [6,5,4,3,2,1])
CPU times: user 1.49 s, sys: 7.16 ms, total: 1.5 s
Wall time: 1.48 s
8264346