您将获得N个高度为1 ... N的块。您可以通过多少种方式将这些块排成一排,这样当从左侧看时,您只能看到L个块(其余部分被较高的块隐藏),从右侧看时您只看到R块?给定N=3, L=2, R=1
的示例只有一个{2, 1, 3}
安排,而N=3, L=2, R=2
有两种方式{1, 3, 2}
和{2, 3, 1}
。
我们应该如何通过编程解决这个问题?有效的方法吗?
答案 0 :(得分:47)
答案 1 :(得分:10)
blocks.py:
import itertools
from collections import defaultdict
def countPermutation(p):
n = 0
max = 0
for block in p:
if block > max:
n += 1
max = block
return n
def countBlocks(n):
count = defaultdict(int)
for p in itertools.permutations(range(1,n+1)):
fwd = countPermutation(p)
rev = countPermutation(reversed(p))
count[(fwd,rev)] += 1
return count
def printCount(count, n, places):
for i in range(1,n+1):
for j in range(1,n+1):
c = count[(i,j)]
if c > 0:
print "%*d" % (places, count[(i,j)]),
else:
print " " * places ,
print
def countAndPrint(nmax, places):
for n in range(1,nmax+1):
printCount(countBlocks(n), n, places)
print
和样本输出:
blocks.countAndPrint(10)
1
1
1
1 1
1 2
1
2 3 1
2 6 3
3 3
1
6 11 6 1
6 22 18 4
11 18 6
6 4
1
24 50 35 10 1
24 100 105 40 5
50 105 60 10
35 40 10
10 5
1
120 274 225 85 15 1
120 548 675 340 75 6
274 675 510 150 15
225 340 150 20
85 75 15
15 6
1
720 1764 1624 735 175 21 1
720 3528 4872 2940 875 126 7
1764 4872 4410 1750 315 21
1624 2940 1750 420 35
735 875 315 35
175 126 21
21 7
1
5040 13068 13132 6769 1960 322 28 1
5040 26136 39396 27076 9800 1932 196 8
13068 39396 40614 19600 4830 588 28
13132 27076 19600 6440 980 56
6769 9800 4830 980 70
1960 1932 588 56
322 196 28
28 8
1
40320 109584 118124 67284 22449 4536 546 36 1
40320 219168 354372 269136 112245 27216 3822 288 9
109584 354372 403704 224490 68040 11466 1008 36
118124 269136 224490 90720 19110 2016 84
67284 112245 68040 19110 2520 126
22449 27216 11466 2016 126
4536 3822 1008 84
546 288 36
36 9
1
你会在问题陈述中注意到一些显而易见的(好的,很明显的)事情:
p
是N-1个块的排列并且具有计数(Lp,Rp),那么插入每个可能的点中的块N的N个排列可以具有从L = 1到Lp +的计数。 1,R = 1至Rp + 1。从经验输出:
最左边的列或最上面的行(其中L = 1或R = 1),其中N个块是 带有N-1个块的行/列:即@ PengOne的符号,
b(N,1,R) = sum(b(N-1,k,R-1) for k = 1 to N-R+1
每条对角线都是一排帕斯卡的三角形,乘以该对角线的常数因子K - 我无法证明这一点,但我确信有人可以 - 即:
b(N,L,R) = K * (L+R-2 choose L-1)
其中K = b(N,1,L+R-1)
因此计算b(N,L,R)的计算复杂度与计算b(N,1,L + R-1)的计算复杂度相同,b是每个三角形中的第一列(或行)
这个观察结果可能是显性解决方案的95%(另外5%我确定涉及标准的组合身份,我对它们并不太熟悉)。
使用Online Encyclopedia of Integer Sequences快速检查表明b(N,1,R)似乎是OEIS sequence A094638:
A094638按行读取的三角形:T(n,k)= | s(n,n + 1-k)|,其中s(n,k)是第一类的带符号斯特林数(1 <= k&lt; ; = n;换句话说,第一类的无符号斯特林数以相反的顺序排列)。 1,1,1,1,3,2,1,6,11,1,1,15,35,50,24,1,15,85,225,274,120,1,21,175,735, 1624,1764,720,1,28,322,1960,6769,13132,13068,5040,1,36,546,4536,22449,67284,118124,109584,40320,1,45,870,9450,63273, 269325,723680,1172900
至于如何有效地计算Stirling numbers of the first kind,我不确定;维基百科给出了一个明确的公式,但它看起来像一个讨厌的总和。这个问题(计算第一种斯特林#s)shows up on MathOverflow看起来像O(n ^ 2),正如PengOne所假设的那样。
答案 2 :(得分:5)
根据@PengOne的回答,这里是my Javascript implementation:
function g(N, L, R) {
var acc = 0;
for (var k=1; k<=N; k++) {
acc += comb(N-1, k-1) * f(k-1, L-1) * f(N-k, R-1);
}
return acc;
}
function f(N, L) {
if (N==L) return 1;
else if (N<L) return 0;
else {
var acc = 0;
for (var k=1; k<=N; k++) {
acc += comb(N-1, k-1) * f(k-1, L-1) * fact(N-k);
}
return acc;
}
}
function comb(n, k) {
return fact(n) / (fact(k) * fact(n-k));
}
function fact(n) {
var acc = 1;
for (var i=2; i<=n; i++) {
acc *= i;
}
return acc;
}
$("#go").click(function () {
alert(g($("#N").val(), $("#L").val(), $("#R").val()));
});
答案 3 :(得分:3)
这是我的构建解决方案,灵感来自@ PengOne的想法。
import itertools
def f(blocks, m):
n = len(blocks)
if m > n:
return []
if m < 0:
return []
if n == m:
return [sorted(blocks)]
maximum = max(blocks)
blocks = list(set(blocks) - set([maximum]))
results = []
for k in range(0, n):
for left_set in itertools.combinations(blocks, k):
for left in f(left_set, m - 1):
rights = itertools.permutations(list(set(blocks) - set(left)))
for right in rights:
results.append(list(left) + [maximum] + list(right))
return results
def b(n, l, r):
blocks = range(1, n + 1)
results = []
maximum = max(blocks)
blocks = list(set(blocks) - set([maximum]))
for k in range(0, n):
for left_set in itertools.combinations(blocks, k):
for left in f(left_set, l - 1):
other = list(set(blocks) - set(left))
rights = f(other, r - 1)
for right in rights:
results.append(list(left) + [maximum] + list(right))
return results
# Sample
print b(4, 3, 2) # -> [[1, 2, 4, 3], [1, 3, 4, 2], [2, 3, 4, 1]]
答案 4 :(得分:1)
我们通过检查特定的测试用例来推导出一般解决方案F(N, L, R)
:F(10, 4, 3)
。
( _ _ _ 10 _ _ _ _ _ _ )
。pos
的变量来跟踪N的位置。pos = 6 ( _ _ _ _ _ 10 _ _ _ _ )
。在10的左侧,有9C5 = (N-1)C(pos-1)
个数字排列。( _ _ 5 _ _ )
中放置5开始,然后按照我们之前所做的步骤进行操作( _ _ 4 _ )
的R-1。3 1 4 2
这样的序列,可以计算像2 4 1 3
F(4, 2, INF)
。pos == 6
为9C5 * F(5, 3, INF) * F(4, 2, INF) = (N-1)C(pos-1) * F(pos-1, L-1, INF)* F(N-pos, R-1, INF)
时的排列数量。 F(5, 3, INF)
中,将在L = 2
的一系列广告位中考虑5,依此类推。L = 1
时返回一个值,即F(N, 1, INF)
必须是基本情况。_ _ _ _ _ 6 7 10 _ _
。
F(5, 1, INF) = 4!
。F(N, 1, INF) = (N-1)!
。Here是用于测试代码的链接
#define INF UINT_MAX
long long unsigned fact(unsigned n) { return n ? n * fact(n-1) : 1; }
unsigned C(unsigned n, unsigned k) { return fact(n) / (fact(k) * fact(n-k)); }
unsigned F(unsigned N, unsigned L, unsigned R)
{
unsigned pos, sum = 0;
if(R != INF)
{
if(L == 0 || R == 0 || N < L || N < R) return 0;
if(L == 1) return F(N-1, R-1, INF);
if(R == 1) return F(N-1, L-1, INF);
for(pos = L; pos <= N-R+1; ++pos)
sum += C(N-1, pos-1) * F(pos-1, L-1, INF) * F(N-pos, R-1, INF);
}
else
{
if(L == 1) return fact(N-1);
for(pos = L; pos <= N; ++pos)
sum += C(N-1, pos-1) * F(pos-1, L-1, INF) * fact(N-pos);
}
return sum;
}