题意:
对于任何一个車A,如果有其他一个車B在它的上方(車B行号小于車A),那么車A必须在車B的右边(車A列号大于車B)。在满足该条件的情况下,摆最多棋子的 方案数有多少。
思路:
自底向上来思考,用n表示行数,m表示列数。先假设一种极端情况(n<m),先摆好前n-1行,如下图。
n:3 m:5(1表示已经放了一个棋子,0表示不放旗子,2表示可以选择放旗子)
1 | 0 | 0 | 0 | 0 |
0 | 1 | 0 | 0 | 0 |
0 | 0 | 2 | 2 | 2 |
设第n-1行摆放棋子为j列。
通过上图可以分析出来,这种情况下,摆放方案有m-j种。
再看另一种情况。
1 | 0 | 0 | 0 | 0 |
0 | 0 | 1 | 0 | 0 |
0 | 0 | 0 | 2 | 2 |
我们先做一个处理,将每个点所处的该行的右边放棋子的方案数都加到自身上,即dp[i][j]+=dp[i][j+1…n](j>=i)
注意!!下面图中的数字为以该点为 左上顶点做棋盘的边界 的
所有方案数
。而不是表示之前放旗子的状态
什么意思呢?就比如该点为(i,j)时,以n-i+1为行数,m-j+1为列数做棋盘的所有方案数。
先从最后一行开始。(前面的n-1行还未处理所以都为0)
0 | 0 | 0 | 0 | 0 |
0 | 0 | 0 | 0 | 0 |
0 | 0 | 3 | 2 | 1 |
当j<i时,不可能有放棋子的可能,因为要求所放的棋子数最大!这点应该很好想。
处理完最后一行,我们怎么把数目转移到n-1行上呢?
其实通过之前的观察我们就知道,如果我们确定一个点要放棋子,那这种情况下,所有的方案数就等于dp[i+1][j+1]。即取决于它右下角的那个位置的方案数。
dp的状态转移公式:
dp[i][j]+=dp[i+1][j+1]+dp[i][j+1]; (i+1<=n && j+1<=m)
我们继续处理完吧✧(≖ ◡ ≖✿),不理解的话,再看看下面的图,或许会比较好理解。
0 | 0 | 0 | 0 | 0 |
0 | 6 | 3 | 1 | 0 |
0 | 0 | 3 | 2 | 1 |
10 | 4 | 1 | 0 | 0 |
0 | 6 | 3 | 1 | 0 |
0 | 0 | 3 | 2 | 1 |
✨✨最后dp[1][1]就是我们要求的答案啦~
附上代码~
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int M=1e9+7;
typedef long long ll;
int n,m;
ll dp[1005][1005];
int main(){
int t;
int nt,mt;
scanf("%d",&t);
while(t--){
scanf("%d%d",&nt,&mt);
n=min(nt,mt);//为了方便处理,都让行数<=列数
m=max(nt,mt);
memset(dp,0, sizeof(dp));
for (int j = m; j >=n ; --j) {
dp[n][j]=1;
}
for (int i = n; i >=1; --i) {
for (int j = m; j >=i ; --j) {
if(j+1<=m && i+1<=n) dp[i][j]=(dp[i][j]+dp[i+1][j+1])%M;
if(j+1<=m) dp[i][j]=(dp[i][j]+dp[i][j+1])%M;
}
}
printf("%I64d\n",dp[1][1]);
}
return 0;
}