洛谷 P5664 Emiya 家今天的饭 题解

  • 纯暴力(32 pts)
    • 开始的第一思路,可以过测试点1-8
    • 直接枚举出每种烹饪方法所用的主要食材
    • 用搜索枚举,传入两个参数:现在枚举的烹饪方法编号以及在此之前的方案数
    • 代码:
#include <iostream>
#include <cstdio>

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 <iostream>
#include <cstdio>

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 <iostream>
#include <cstdio>

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 <iostream>
#include <cstdio>
#include <cstring>

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 <iostream>
#include <cstdio>
#include <cstring>

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 ;
} 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值