http://poj.org/problem?id=2663

此题是北大算法课的递归作业。想了半天也找不出递推公式。网上的文章都是直接给出结论,而没有推导过程,无异于耍流氓。
继续寻找,直到看到此文才终于明白。

思路

首先,列数n肯定要是偶数,因为n*3/2得是整数。

然后我们来想递推公式。因为n是偶数,所以n-1, n-3这种奇数列,必然无法完全覆盖。我们不考虑。

首先,f(n)可以由f(n-2)推得,则剩下的两列还有三种方法。
clipboard.png

所以f(n)至少要等于3 * f(n-2).

那么f(n)可不可以由f(n-4)推得呢?对于剩下的4列,如果是上图中任两种情况的组合,那它实际上就还是f(n-2)中的情况,因此重合了。必须要找到一种无法被拆成完美2列的4列图形。也就是说,单看两列时是参差不齐的。这时才不与f(n-2)的情形重合。

那么有没有这种图形呢?见下图:

clipboard.png

同理,6列的类似图形是这样:

clipboard.png

可以看出这种图形的特点:为了避免能被拆成2列的图形,在内部要有参差不齐。方法就是一行平躺,然后其余两行两边竖着放,中间横着放。尝试几次后就会发现只有这种方案。

因为平躺的那行可以在上面也可以在下面,因此无法分割的4列、6列、8列……的排列方法都是有两种。

由此,我们便能写出递推公式:

f(2) = 3,
f(4) = 3 * f(2) + 2 * 1,
f(6) = 3 * f(4) + 2 * (f(2) + 1), // 后半部分是不可拆分4列和不可拆分6列的形状数量。
f(n) = 3 * f(n-2) + 2 * (f(n-4) + f(n-6) + ... + f(2) + 1)
     = f(n-2) + 2 * (f(n-2) + f(n-4) + ... + f(2) + 1)

代码

#include<iostream>
using namespace std;

// 递归计算
int calc(int n)
{
    int sum = 0;
    if (n % 2) return 0;
    if (n == 0)
        return 1; // 结果没有意义,但系统要求是1.
    else if (n == 2)
        return 3;    
    else if (n > 2)
    {
        sum += 3 * calc(n - 2);
        while (n -= 2, n - 2 > 0)
            sum += 2 * calc(n-2);
        sum += 2;
    }
    return sum;
}

int main()
{
    int n;
    while (scanf("%d",&n), n != -1)
    {
        int ans = calc(n);
        printf("%d\n", ans);
    }
    return 0;
}

优化

上面程序已经能通过。但递归写法会造成重复计算。可以用一个数组保存计算结果。
另外既然答案要求f(0) = 1,则可以将递推公式写成:

f(n) = 3 * f(n-2) + 2 * (f(n-4) + f(n-6) + ... + f(0))

将f(n)与f(n-2)相减可得

f(n) = 4 * f(n-2) - f(n-4)

这下就简单多了。
可以用迭代算出:

int f[30],n;
void init()
{
    int i;
    f[0]=1; f[2]=3;
    for(i=4; i<=30; i+=2)
        f[i] = 4 * f[i-2] - f[i-4];
}

Toconscience
153 声望39 粉丝