- 纯暴力(32 pts)
- 开始的第一思路,可以过测试点1-8
- 直接枚举出每种烹饪方法所用的主要食材
- 用搜索枚举,传入两个参数:现在枚举的烹饪方法编号以及在此之前的方案数
- 代码:
#include
#include
const long long mod = 998244353 ;
using namespace std ;
int n , m ;
int a[105][2005] ;
int cnt[2005] ;
long long ans ;
void input()
{
scanf("%d%d" , &n , &m) ;
for ( int i = 1 ; i <= n ; i++ )
{
for ( int j = 1 ; j <= m ; j++ )
{
scanf("%d" , &a[i][j]) ;
}
}
}
void dfs(int x , long long sum)
{
if ( x > n )
{
long long num = 0 ;
for ( int i = 1 ; i <= m ; i++ )
{
num += cnt[i] ;
}
num /= 2 ;
for ( int i = 1 ; i <= m ; i++ )
{
if ( cnt[i] > num )
return ;
}
ans += sum ;
ans = ans % mod ;
return ;
}
dfs(x + 1 , sum) ;
for ( int i = 1 ; i <= m ; i++ )
{
if ( a[x][i] == 0 )
continue ;
cnt[i]++ ;
dfs(x + 1 , sum * a[x][i] % mod) ;
cnt[i]-- ;
}
}
int main()
{
input() ;
dfs(1 , 1) ;
printf("%lld\n" , ans - 1) ;
return 0 ;
}
- 用dp过9-12(48pts)
- 在写完32pts后,想先考虑m = 2(显然,m = 2会比其他简单),发现只有两种主要食材,所以必须选第一种食材的次数等于选第二种食材的次数
- 想到包裹分拣,可以开三位分别表示现在决策的烹饪方法编号、现在选的主要食材一的个数以及现在选的主要食材二的个数
- 之后动态规划一下就可以了
- 代码:
#include
#include
const long long mod = 998244353 ;
using namespace std ;
int n , m ;
int a[105][2005] ;
int cnt[2005] ;
long long ans ;
long long dp[105][55][55] ;
void input()
{
scanf("%d%d" , &n , &m) ;
for ( int i = 1 ; i <= n ; i++ )
{
for ( int j = 1 ; j <= m ; j++ )
{
scanf("%d" , &a[i][j]) ;
}
}
}
void dfs(int x , long long sum)
{
if ( x > n )
{
long long num = 0 ;
for ( int i = 1 ; i <= m ; i++ )
{
num += cnt[i] ;
}
num /= 2 ;
for ( int i = 1 ; i <= m ; i++ )
{
if ( cnt[i] > num )
return ;
}
ans += sum ;
ans = ans % mod ;
return ;
}
dfs(x + 1 , sum) ;
for ( int i = 1 ; i <= m ; i++ )
{
if ( a[x][i] == 0 )
continue ;
cnt[i]++ ;
dfs(x + 1 , sum * a[x][i] % mod) ;
cnt[i]-- ;
}
}
int main()
{
input() ;
if ( n <= 10 )
{
dfs(1 , 1) ;
printf("%lld\n" , ans - 1) ;
return 0 ;
}
if ( m == 2 )
{
ans = 0 ;
dp[0][0][0] = 1 ;
for ( int i = 1 ; i <= n ; i++ )
{
for ( int j = 0 ; j <= n / 2 ; j++ )
{
for ( int k = 0 ; k <= n / 2 ; k++ )
{
dp[i][j][k] = dp[i - 1][j][k] ;
if ( j > 0 )
{
dp[i][j][k] = (dp[i][j][k] + (dp[i - 1][j - 1][k] * a[i][1]) % mod) % mod ;
}
if ( k > 0 )
{
dp[i][j][k] = (dp[i][j][k] + (dp[i - 1][j][k - 1] * a[i][2]) % mod) % mod ;
}
}
}
}
for ( int i = 1 ; i <= n / 2 ; i++ )
{
ans = ( ans + dp[n][i][i] ) % mod ;
}
printf("%lld\n" , ans) ;
return 0 ;
}
return 0 ;
}
- m = 3(64pts)
- 由m = 2的dp想到m = 3也可用dp,开四维处理即可
- 直接dp一下即可
- 代码几乎与m = 2相同
#include
#include
const long long mod = 998244353 ;
using namespace std ;
int n , m ;
int a[105][2005] ;
int cnt[2005] ;
long long ans ;
long long dp[105][55][55] ;
long long dp2[105][55][55][55] ;
void input()
{
scanf("%d%d" , &n , &m) ;
for ( int i = 1 ; i <= n ; i++ )
{
for ( int j = 1 ; j <= m ; j++ )
{
scanf("%d" , &a[i][j]) ;
}
}
}
void dfs(int x , long long sum)
{
if ( x > n )
{
long long num = 0 ;
for ( int i = 1 ; i <= m ; i++ )
{
num += cnt[i] ;
}
num /= 2 ;
for ( int i = 1 ; i <= m ; i++ )
{
if ( cnt[i] > num )
return ;
}
ans += sum ;
ans = ans % mod ;
return ;
}
dfs(x + 1 , sum) ;
for ( int i = 1 ; i <= m ; i++ )
{
if ( a[x][i] == 0 )
continue ;
cnt[i]++ ;
dfs(x + 1 , sum * a[x][i] % mod) ;
cnt[i]-- ;
}
}
int main()
{
input() ;
if ( n <= 10 )
{
dfs(1 , 1) ;
printf("%lld\n" , ans - 1) ;
return 0 ;
}
if ( m == 2 )
{
ans = 0 ;
dp[0][0][0] = 1 ;
for ( int i = 1 ; i <= n ; i++ )
{
for ( int j = 0 ; j <= n / 2 ; j++ )
{
for ( int k = 0 ; k <= n / 2 ; k++ )
{
dp[i][j][k] = dp[i - 1][j][k] ;
if ( j > 0 )
{
dp[i][j][k] = (dp[i][j][k] + (dp[i - 1][j - 1][k] * a[i][1]) % mod) % mod ;
}
if ( k > 0 )
{
dp[i][j][k] = (dp[i][j][k] + (dp[i - 1][j][k - 1] * a[i][2]) % mod) % mod ;
}
}
}
}
for ( int i = 1 ; i <= n / 2 ; i++ )
{
ans = ( ans + dp[n][i][i] ) % mod ;
}
printf("%lld\n" , ans) ;
return 0 ;
}
else if ( m == 3 )
{
dp2[0][0][0][0] = 1 ;
for ( int i = 1 ; i <= n ; i++ )
for ( int j = 0; j <= n; j++ )
for ( int k = 0 ; k <= i - j ; k++ )
for ( int l = 0 ; l <= i - j - k ; l++ )
{
dp2[ i ][ j ][ k ][ l ] =
dp2[ i - 1 ][ j ][ k ][ l ] ;
if ( j >= 1 )
dp2[ i ][ j ][ k ][ l ] +=
dp2[ i - 1 ][ j - 1 ][ k ][ l ] *
a[ i ][ 1 ] % mod ;
if ( k >= 1 )
dp2[ i ][ j ][ k ][ l ] +=
dp2[ i - 1 ][ j ][ k - 1 ][ l ] *
a[ i ][ 2 ] % mod ;
if ( l >= 1 )
dp2[ i ][ j ][ k ][ l ] +=
dp2[ i - 1 ][ j ][ k ][ l - 1 ] *
a[ i ][ 3 ] % mod ;
}
for ( int i = 0 ; i <= n / 2 ; i++ )
for ( int j = 0 ; j <= n / 2 ; j++ )
for ( int k = 0 ; k <= n / 2 ; k++ )
{
if ( i <= j + k && j <= i + k && k <= i + j )
ans += dp2[ n ][ i ][ j ][ k ] ;
ans = ans % mod ;
}
printf( "%lld\n" , ans - 1 ) ;
}
return 0 ;
}
- n = 40 && m = 500 (84pts)
- 发现Yazid的条件是相对较难做到,从Yazid的条件下手讨论
- 做完64pts后,感觉不太容易用顺向思维p做(m = 500肯定会炸空间)
- 于是想到逆推(以)考虑,这是先算出总方案(定义sum[i]表示用第i种烹饪方法所能做出的总方案数,则总方案数为(sum[1] + 1)×(sum[2] + 1)× …… ×(sum[n] + 1))
- 如果不满足Yazid的条件,则必然有一种菜品作为主要食材用了至少n / 2 + 1次,枚举这种菜品,之后将菜品分为主菜(即枚举出的菜品)与非主菜,显然这其中每类没有交集,所以可直接求出不满足Yazid的条件的总和,相减再减一即可
- 其中枚举过程中只要用m = 2的解法即可
- 代码:
#include
#include
#include
using namespace std ;
const int mod = 998244353 ;
const int N = 2100 ;
int n , m ;
int x[105][2005] ;
long long sum[105] ;
long long a[2005] , b[2005] ;
long long dp[105][105][105] ;
void input()
{
scanf("%d%d" , &n , &m) ;
for ( int i = 1 ; i <= n ; i++ )
{
for ( int j = 1 ; j <= m ; j++ )
{
scanf("%d" , &x[i][j]) ;
}
}
return ;
}
void doit()
{
long long tot = 1 ;
for ( int i = 1 ; i <= n ; i++ )
{
sum[i] = 0 ;
for ( int j = 1 ; j <= m ; j++ )
{
sum[i] += x[i][j] ;
sum[i] %= mod ;
}
tot = ( tot * ( sum[i] + 1 ) ) % mod ;
}
long long wrong = 0 ;
for ( int food = 1 ; food <= m ; food++ )
{
memset(dp , 0 , sizeof(dp)) ;
for ( int i = 1 ; i <= n ; i++ )
a[i] = x[i][food] , b[i] = (sum[i] - a[i] + mod) % mod ;
dp[0][0][0] = 1 ;
for ( int i = 1 ; i <= n ; i++ )
for ( int j = 0 ; j <= i ; j++ )
for ( int k = 0 ; k <= i - j ; k++ )
{
dp[i][j][k] = dp[i - 1][j][k] ;
if ( j > 0 )
{
dp[i][j][k] = (dp[i][j][k] + (dp[i - 1][j - 1][k] * a[i]) % mod) % mod ;
}
if ( k > 0 )
{
dp[i][j][k] = (dp[i][j][k] + (dp[i - 1][j][k - 1] * b[i]) % mod) % mod ;
}
}
for ( int i = 1 ; i <= n ; i++ )
for ( int j = 0 ; j <= n - i ; j++ )
if ( i > j )
wrong += dp[n][i][j] ;
wrong %= mod ;
}
printf("%lld\n" , (tot - 1 - wrong + mod) % mod) ;
}
int main()
{
input() ;
doit() ;
return 0 ;
}
- 100pts
- 思路与84pts的几乎一样,只是发现dp其实关心的只有差,所以可以用差值dp优化为O(n²),总复杂度O(n³)
- 注意差值dp时必须平移,因为可能差为负数
- 代码:
#include
#include
#include
using namespace std ;
const int mod = 998244353 ;
const int N = 2100 ;
int n , m ;
int x[105][2005] ;
long long sum[105] ;
long long a[2005] , b[2005] ;
long long dp[105][2105] ;
void input()
{
scanf("%d%d" , &n , &m) ;
for ( int i = 1 ; i <= n ; i++ )
{
for ( int j = 1 ; j <= m ; j++ )
{
scanf("%d" , &x[i][j]) ;
}
}
return ;
}
void doit()
{
long long tot = 1 ;
for ( int i = 1 ; i <= n ; i++ )
{
sum[i] = 0 ;
for ( int j = 1 ; j <= m ; j++ )
{
sum[i] += x[i][j] ;
sum[i] %= mod ;
}
tot = ( tot * ( sum[i] + 1 ) ) % mod ;
}
long long wrong = 0 ;
for ( int food = 1 ; food <= m ; food++ )
{
memset(dp , 0 , sizeof(dp)) ;
for ( int i = 1 ; i <= n ; i++ )
a[i] = x[i][food] , b[i] = (sum[i] - a[i] + mod) % mod ;
dp[0][N] = 1 ;
for ( int i = 1 ; i <= n ; i++ )
{
for ( int j = -1 * i ; j <= i ; j++ )
{
dp[i][N + j] = dp[i - 1][N + j] ;
if ( N + j - 1 >= 0 )
dp[i][N + j] += ( dp[i - 1][N + j - 1] * a[i] % mod ) ;
dp[i][N + j] += ( dp[i - 1][N + j + 1] * b[i] % mod ) ;
dp[i][N + j] %= mod ;
}
}
for ( int i = 1 ; i <= n ; i++ )
wrong += dp[n][N + i] ;
wrong %= mod ;
}
printf("%lld\n" , (tot - 1 - wrong + mod) % mod) ;
}
int main()
{
input() ;
doit() ;
return 0 ;
}