http://poj.org/problem?id=2663
此题是北大算法课的递归作业。想了半天也找不出递推公式。网上的文章都是直接给出结论,而没有推导过程,无异于耍流氓。
继续寻找,直到看到此文才终于明白。
思路
首先,列数n肯定要是偶数,因为n*3/2得是整数。
然后我们来想递推公式。因为n是偶数,所以n-1, n-3这种奇数列,必然无法完全覆盖。我们不考虑。
首先,f(n)可以由f(n-2)推得,则剩下的两列还有三种方法。
所以f(n)至少要等于3 * f(n-2).
那么f(n)可不可以由f(n-4)推得呢?对于剩下的4列,如果是上图中任两种情况的组合,那它实际上就还是f(n-2)中的情况,因此重合了。必须要找到一种无法被拆成完美2列的4列图形。也就是说,单看两列时是参差不齐的。这时才不与f(n-2)的情形重合。
那么有没有这种图形呢?见下图:
同理,6列的类似图形是这样:
可以看出这种图形的特点:为了避免能被拆成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];
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。