回溯 - 用硬币填充网格

时间:2012-07-23 18:04:47

标签: c++ algorithm backtracking

我正在尝试在查找面试问题时遇到这个问题。我们被问到将r币放在n * m网格上的方式的数量,使得每行和每列包含至少一个硬币。

我想到了一个回溯解决方案,按行主要顺序处理网格中的每个单元格,我已经以这种方式设置了我的递归。似乎我的方法有问题,因为它每次都输出0。有人可以帮我找到我的方法中的错误。 ?感谢。

约束。 n,m< 200和r< n * m个;

这是我想出的代码。

#include<cstdio>
#define N 201
int n, m , r;
int used[N][N];
int grid[N][N] ;  // 1 is coin is placed . 0 otherwise. // -1 undecided.

bool isOk()
{
    int rows[N];
    int cols[N];

    for(int i = 0 ; i < n ; i++) rows[i] = 0;
    for(int i = 0 ; i < m ; i++) cols[i] = 0;
    int sum = 0;
    for(int i = 0 ; i < n ; i++)for(int j = 0; j < m ; j++)
    {
        if(grid[i][j]==1)
        {
            rows[i]++;
            cols[j]++;
            sum++;  
        }
    }
    for(int i = 0 ; i < n ; i++) 
    {
        if(rows[i]==0) return false;
    }

    for(int j = 0 ; j < n ; j++)
    {
        if(cols[j]==0) return false;
    }
    if(sum==r) return true;
    else return false;
}

int calc_ways(int row , int col,  int coins)
{
    if(row >= n) return 0;
    if(col >= m) return 0;
    if(coins > r) return 0;
    if(coins == r) 
    {
        bool res = isOk();
        if(res) return 1; 
        else 0;
    }

    if(row == n - 1 and col== m- 1) 
    {
        bool res = isOk();
        if(res) return 1;
        else return 0;
    }

    int nrow, ncol;

    if(col + 1 >= m)
    {
        nrow = row + 1;
        ncol = 0;
    }
    else
    {
        nrow = row;
        ncol = col + 1;
    }
    if(used[row][col]) return calc_ways(nrow, ncol, coins);
    int ans =  0;
    used[row][col] = 1;
    grid[row][col] = 0;
    ans += calc_ways(nrow , ncol , coins);

    grid[row][col] = 1;
    ans += calc_ways(nrow , ncol , coins + 1);

    return ans;
}

int main()
{
    int t;
    scanf("%d" , &t);
    while(t--)
    {
        scanf("%d %d %d" , &n , &m , &r);
        for(int i = 0 ; i <= n ; i++)
        {
            for(int j = 0; j <= m ; j++)
            {
                used[i][j] = 0;
                grid[i][j] = -1;
            }
        }
        printf("%d\n" , calc_ways(0  ,  0 , 0 ));
    }
    return 0;
}

3 个答案:

答案 0 :(得分:3)

你几乎不需要一个程序来解决这个问题。

不失一般性,让m&lt; = n。

首先,我们必须有n&lt; = r,否则无法解决问题。

然后,我们将问题细分为一个大小为mxm的正方形,我们将沿主要对角线放置m个硬币,剩下的就是我们将放置n-m个硬币的剩余部分,以便满足剩余条件

有一种方法可以将硬币放在广场的主要对角线上。

余数有m ^(n - m)种可能性。 到目前为止,我们可以在n中排列总数!方式,虽然其中一些将是重复的(剩下多少作为学生的练习)。

此外,还有剩下的r - n个硬币和剩下的(m - 1)n个地方。

将这些全部放在一起我们有

的上限
1 x m^(n - m) x n! x C((m - 1)n, r - n)

问题的解决方案。将此数字除以重复排列的数量,您就完成了。

答案 1 :(得分:0)

问题1

代码将首先在每个正方形上放置一枚硬币并标记每个正方形。

然后测试最终位置并确定最终位置不符合r币的目标。

接下来它将开始回溯,但实际上永远不会尝试其他选择,因为使用[row] [col]设置为1并且这会使代码短路以放置硬币。

换句话说,一个问题是“used”中的条目已设置,但在递归期间从未清除。

问题2

代码的另一个问题是,如果n,m的大小为200,那么它将永远不会完成。

问题是这个回溯代码具有复杂度O(2 ^(n * m)),因为它将尝试放置硬币的所有可能组合(n = m = 200的许多宇宙生命周期......)。

我建议你看一下不同的方法。例如,您可能需要考虑动态编程来计算在板的剩余“a”列上放置“k”硬币的方式,以便我们确保将硬币放在“b”行的“b”行上。目前没有硬币的董事会。

答案 2 :(得分:0)

它可以被视为d网格可以用r硬币填充的总方式 - (总的方式留下单行和填充d休息 - 总体方式留下单个列并填充d休息 - 总方式离开行nd列和nd填充d rest)暗示

p(n*m ,r) -( (p((n-1)*m , r) * c(n,1)) +(p((m-1)*n , r) * c(m,1))+(p((n-1)*(m-1) , r) * c(n,1)*c(m,1)) )

我只是这么认为,但不确定!