题意:有高为1, 2, 3,…, n的杆子各一根排成一行。从左边能看到l根,从右边能看到r根,求有多少种可能。
分析:
设d(i,j,k)表示让高度为1~i根杆子排成一行,从左边能看到j根,从右边能看到k根的方
案数。为了方便起见,假定i≥2。如何进行递推呢?首先尝试按照从小到大的顺序按照各个
杆子。假设已经安排完高度为1~i-1的杆子,那么高度为i的杆子可能会挡住很多其他杆子,
看上去很难写出递推式。
那么换一个思路:按照从大到小的顺序安排各个杆子。假设已经安排完高度为2~i的杆
子,那么高度为1的杆子不管放哪里都不会挡住任何一根杆子。有如下3种情况。
情况1:插到最左边,则从左边能看到它,从右边看不见(因为i≥2)。
情况2:如果插到最右边,则从右边能看到它,从左边看不见。
情况3(有i-2个插入位置):插到中间,则不管从左边还是右边都看不见它。
在第一种情况下,高度为2~i的那些杆子必须满足:从左边能看到j-1根,从右边能看
到k根,因为只有这样,加上高度为1的杆子之后才是“从左边能看到j根,从右边能看
到k根”。虽然状态d(i,j,k)表示的是“让高度为1~i的杆子……”,而现在需要把高度为2~i+1
的杆子排成一行,但是不难发现:其实杆子的具体高度不会影响到结果,只要有i根高度各
不相同的杆子,从左从右看分别能看到j根和k根,方案数就是d(i,j,k)。换句话说,情况1对应
的方案数是d(i-1,j-1,k)。类似地,情况2对应的方案数是d(i-1,j,k-1),而情况3对应的方案数
是d(i-1,j,k)*(i-2)。这样,就得到了如下递推式:
<nobr> d(i,j,k)=d(i−1,j−1,k)+d(i−1,j,k−1)+d(i−1,j,k)∗(i−2) </nobr>
代码如下:
#include <bits/stdc++.h>
using namespace std;
#define rep(i, a, b) for(int i = a; i <= b; i++)
const int maxn = 20;
long long dp[maxn+1][maxn+1][maxn+1];
void pre_init(){
dp[1][1][1] = 1;
rep(i, 2, maxn){
rep(j, 1, i){
rep(k, 1, i){
dp[i][j][k] = dp[i-1][j][k] * (i - 2); //3
if(j > 1) dp[i][j][k] += dp[i-1][j-1][k]; //1
if(k > 1) dp[i][j][k] += dp[i-1][j][k-1]; //2
}
}
}
}
int main()
{
pre_init();
int T, n, l, r;
scanf("%d", &T);
while(T--)
{
scanf("%d%d%d", &n, &l, &r);
cout << dp[n][l][r] << endl;
}
return 0;
}