Algorithm 一种计算整数网格数的有效算法

Algorithm 一种计算整数网格数的有效算法,algorithm,combinatorics,Algorithm,Combinatorics,考虑一个由非负整数组成的3乘3的正方形网格。对于每一行i,整数之和设置为r\u i。类似地,对于每列j,该列中的整数之和设置为c_j。因此,问题的一个实例由6非负整数描述 有没有一个有效的算法来计算有多少不同的 在网格中,整数的赋值是给定行和列的 总和约束 显然,我们可以枚举所有可能的非负整数矩阵,其值最高可达sum r_i,并检查每个矩阵的约束条件,但这将非常缓慢 示例 假设行约束为1 2 3,列约束为3 2 1。可能的整数网格为: ┌─────┬─────┬─────┬─────┬─────

考虑一个由非负整数组成的3乘3的正方形网格。对于每一行
i
,整数之和设置为
r\u 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时,但更一般的解决方案将非常有趣

在给定行和列和约束的情况下,是否有一种有效的算法来计算网格中有多少不同的整数赋值

upd
N
被修复时(即变为常数
3
),我对这个特定问题的回答是错误的。在这种情况下,它是多项式。很抱歉提供了误导性的信息

TL;DR:我认为这至少是NP难的。没有多项式算法,但可能有一些启发式加速


对于N×N网格,您有行和的
N
方程,
N
列和的方程和
N^2
非负约束:

对于
N>2
此系统通常有多个可能的解决方案。因为有
N^2
未知变量
x_ij
2N
方程=>对于
N>2
N^2>2N

您可以消除
2N-1
变量,只留下一个等式,其中
K=N^2-(2N-1)
变量获得总和
S
。然后,您必须找出
K
术语的所有可能组合,以获得
S
。这个问题是NP完全问题。组合的数量不仅取决于术语的数量
K
,而且还取决于值
S
的顺序

这个问题提醒了我。我的第一个想法是使用类似的方法只找到一个解,然后遍历凸面的边以找到所有可能的解。我希望有一个最佳的算法。但不是,整数单纯形法,与之相关,是NP难的:(


我希望,有一些相关问题的启发式方法可以用来加速原始蛮力解决方案。

我不知道匹配算法,但我认为解决一个匹配算法不会那么困难。给定任何一个解决方案,您可以通过选择网格矩形区域的四个角来导出另一个解决方案,递增g将两个对角点乘以某个值,并将另两个对角点减去该值。该值的范围将受到每个对角对的最小值的约束。如果确定所有此类范围的大小,则应能够将它们相乘,以确定总的可能解

假设将网格描述为一个熟悉的电子表格,按字母顺序表示列,按数字顺序表示行,则可以在以下列表中描述所有可能的区域:

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嵌套循环解可能足够快

函数求解(行和、列和){
var计数=0;

对于(var a=0;a我已经厌倦了优化slow选项。我得到了所有的组合,更改代码只是为了得到总计数。这是我能得到的最快的:

    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
私有静态整数计数(int[]行和,int[]列和)
{
整数计数=0;
int[]行0=新int[3];
整数和=行和[0];

对于(int r0=0;r0它将无助于解决#p-hard问题(如果您允许矩阵具有任何大小——请参阅下面评论中的参考),但有一个解决方案并不等于枚举所有矩阵,而是一组更小的对象。根据您的输入,它可能会更快,但仍然具有指数复杂性。因为它是几本代数组合学书籍或Knuth的AOCP 3中的一整章,所以我在这里不详细介绍,只针对e相关维基百科页面

其思想是使用这些矩阵中的每一个矩阵与一对形状相同的表进行双射,其中一个表由行和计数的整数填充,另一个表由列和计数。形状为U的表由V计数的数字填充,称为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

撇开我使用Robinson Schensted Knuth双射的另一个答案不谈,这里是 另一个解决方案不需要高级组合数学,但需要一些技巧 编程可以解决任意la的这个问题
 # 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