题目
给出n*m的只含0 1 的 棋盘
问从1 1 开始走到n m 的所有方案中至少有 p 个 0 和 q 个 0 的所有方案数。
只能往下或者往右走。
0 <= n , m <= 500
答案模998244353
题解思路
这个取模在提醒我们方案数会很大,bfs肯定不行。
所以我们转移思路到dp
dp [ i ][ j ] [ k ]
表示从1 1 走到 i j 的 所有恰好包含 k 个 0 的方案数。
因为只能走两个方向,所以我们很容易推出转移方程。
但是这样内存会爆炸(牛客网在你开的时候不会提示你,用着的时候才会报错)
因为我们只需要当前层和上一层 。
所以我们可以利用滚动数组来优化。
if ( mp[i][j] == 1 )
dp[i&1][j][k] = ( dp[(i-1)&1][j][k] + dp[i&1][(j-1)][k] )%mod ;
else
dp[i&1][j][k] = ( dp[(i-1)&1][j][k-1] + dp[i&1][(j-1)][k-1] )%mod ;
如果k从0开始更新
尽管可能会是-1,答案还是对的,而且不报错。
牛客真牛逼,我服了。
所以正确而且安全的写法应该是修改定义为k-1个0 ,并且让k从1开始更新。
AC代码
#include<cstdio>
#include<algorithm>
#include <iostream>
using namespace std ;
const int mod = 998244353 ;
int dp[3][510][1010] ;
bool mp[510][510] ;
int main ()
{
int n , m , p , q ;
scanf("%d%d%d%d",&n,&m,&p,&q);
for ( int i = 1 ; i <= n ; i++ )
for ( int j = 1 ; j <= m ; j++ )
scanf("%d",&mp[i][j]);
if ( mp[1][1] == 1 )
dp[1][1][1] = 1 ;
else
dp[1][1][2] = 1 ;
for ( int i = 1 ; i <= n ; i++ )
for ( int j = 1 ; j <= m ; j++ )
{
for ( int k = 1 ; k <= i+j1 ; k++ )
{
if ( i == 1 && j == 1 )
break ;
if ( mp[i][j] == 1 )
dp[i&1][j][k] = ( dp[(i-1)&1][j][k] + dp[i&1][(j-1)][k] )%mod ;
else
dp[i&1][j][k] = ( dp[(i-1)&1][j][k-1] + dp[i&1][(j-1)][k-1] )%mod ;
}
}
long long ans = 0 ;
for (int i = p+1 ; i <= n+m-q ; i++ )
ans = ( ans + dp[n&1][m][i] )%mod ;
printf("%lld\n",ans) ;
return 0 ;
}