【NOIP模拟】table(动态规划)
题目背景
SOURCE:NOIP2016-RZZ-2 T2
题目描述
给定一个 n×m 的矩阵,行列均从 1 开始标号。
一个矩阵被认为是稳定的,当且仅当对于任意的 2≤i≤n,第 i 行的数的和不小于第 i−1 行的数的和,且最后一行的数的和小于等于 m ,并且要求矩阵中所有的元素都是非负的。
求所有 n×m 的稳定矩阵的方案数,答案对 109 取模。
输入格式
第一行一个整数 T ,表示数据组数。
每组数据一行两个整数 n,m 。
输出格式
输出 T 行,每行一个整数,表示方案数。
样例数据 1
输入
3
1 1
2 2
2 3
输出
2
25
273
备注
【数据规模与约定】
对于 30% 的数据,n,m≤3。
对于 60% 的数据,n,m≤50。
对于 100% 的数据,1≤n,m≤2000;1≤T≤10。
【题目分析】
题目意思显而易见,求两次dp:
第一次,求出$C(i, j)$:
对于这一次dp,其实就是求组合数,可以使用递推公式: $$C(i, j) = C(i - 1, j - 1) + C(i - 1, j)$$
那么我们在求将$i$个数和为$j$的方案数时, 实际就是将$j$个1分成$i$份,可以看作从$j + i - 1$个数中选出$i - 1$个数(作为栅栏将1隔开)
其实就是求$C(j + i - 1, i - 1)$
第二次,求出$dp(i, j)$表示第$i$行和小于等于$j$的方案数。
这一次dp采用前缀和累加记录。对于当前$dp(i, j)$, 先加上$dp(i, j - 1)$(累加,这样算出来的才是小于等于j的方案数),
然后再加上$C(j + m - 1,m - 1) × dp(i - 1, j)$(上一行的和小于等于当前行的和, 则当前行的和为$j$时(方案数$C(j + m - 1,m - 1)$) ×上一行的和小于等于$j$(方案数$dp(i - 1, j)$).
不要忘记取模(乘法爆int 先乘上 1LL )。
【code】
#include<iostream> #include<cstdio> #include<cstdlib> #include<cstring> #include<string> #include<algorithm> #include<vector> using namespace std; const int N = 2005, Mod = 1e9; int n, m, T; int cnt[N<<1][N<<1], s[N][N]; inline void init(){ for(int i = 0; i <= 4000; i++) cnt[i][0] = cnt[i][i] = 1; for(int i = 2; i <= 4000; i++) for(int j = 1; j < i; j++) cnt[i][j] = (cnt[i - 1][j - 1] + cnt[i - 1][j]) % Mod; } int main(){ init(); // cout<<sum[2][2]<<" "<<sum[2][1]<<" "<<sum[2][0];return 0; cin>>T; while(T--){ cin>>n>>m; memset(s, 0, sizeof s); for(int i = 0; i <= m; i++) s[0][i] = 1; for(int i = 1; i <= n; i++){ for(int j = 0; j <= m; j++){ long long tmp = s[i - 1][j]; tmp = (1LL * tmp * cnt[j + m - 1][m - 1]) % Mod; if(j) s[i][j] = (tmp + s[i][j - 1]) % Mod; else s[i][j] = tmp; } } cout<<s[n][m]<<endl; } return 0; }