「CSP-S 2019」 Emiya 家今天的饭 题解

题面

在这里插入图片描述
在这里插入图片描述

样例

2 3 
1 0 1
0 1 1
3

分析

考虑正着限制最大的数不超过一半不好做,那我们可以反着来。

d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k] 为第 i i i 行,此列指定数 p o i n t point point 数量为 j j j 个,选的非指定数的个数为 k k k 的方案数。

则一共有三种情况

C a s e   1 : Case\ 1: Case 1: d p [ i ] [ j ] [ k ] = d p [ i − 1 ] [ j ] [ k ] dp[i][j][k] = dp[i - 1][j][k] dp[i][j][k]=dp[i1][j][k] (此列不选数字)

C a s e   2 : d p [ i ] [ j ] [ k ] = d p [ i ] [ j ] [ k ] + d p [ i − 1 ] [ j ] [ k − 1 ] ∗ ( p r e [ i ] − a [ i ] [ p o i n t ] ) Case\ 2:dp[i][j][k] = dp[i][j][k] + dp[i - 1][j][k - 1] * (pre[i] - a[i][point]) Case 2:dp[i][j][k]=dp[i][j][k]+dp[i1][j][k1](pre[i]a[i][point])(此列选不为 p o i n t point point 的其他数字)

C a s e   3 : d p [ i ] [ j ] [ k ] = ( d p [ i ] [ j ] [ k ] + d p [ i − 1 ] [ j − 1 ] [ k ] ∗ a [ i ] [ p o i n t ] ) Case\ 3:dp[i][j][k] = (dp[i][j][k] + dp[i - 1][j - 1][k] * a[i][point]) Case 3:dp[i][j][k]=(dp[i][j][k]+dp[i1][j1][k]a[i][point])(此列选 p o i n t point point

r e s res res 极为 d p [ n ] [ j ] [ k ] ( j > k ) dp[n][j][k](j>k) dp[n][j][k](j>k)

这时我们把总情况算出来:

d p [ i ] = d p [ i − 1 ] × ( p r e [ i ] + 1 ) dp[i]=dp[i-1]\times(pre[i]+1) dp[i]=dp[i1]×(pre[i]+1)

d p [ n ] − − dp[n] -- dp[n]

答案即为 d p [ n ] − r e s dp[n]-res dp[n]res

但是这样是 O ( n 3 m ) O(n^3m) O(n3m),只有 84 p t s 84pts 84pts

考虑优化:会发现,对于 j j j k k k,我们不用考虑他们具体的数值,而考虑他们的相对关系即可。

于是定义 d p [ i ] [ j ] dp[i][j] dp[i][j] 为第 i i i 行, p o i n t point point 数量与其他数的数量差为 j j j 的方案数。

然后,你懂得吧。。。

Code

#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#define LL long long
using namespace std;
const int Mod = 998244353, MAXN = 105, MAXM = 2e3 + 5;
int a[MAXN][MAXM], n, m, t;
LL dp[MAXN][MAXN << 1], pre[MAXN], ans, res;
// 考虑优化:会发现,对于 j 和 k,我们不用考虑他们具体的数值,而考虑他们的相对关系即可。
int main() {
//	freopen("meal.in", "r", stdin);
//	freopen("meal.out", "w", stdout);
	scanf("%d%d", &n, &m); ans = 1; t = n - (n / 2) - 1; dp[0][n] = 1;
	for(int i = 1; i <= n; i ++) for(int j = 1; j <= m; j ++) scanf("%d", &a[i][j]), pre[i] += a[i][j], pre[i] %= Mod;
	for(int i = 1; i <= n; i ++) ans *= (pre[i] + 1), ans %= Mod; ans --;
	for(int point = 1; point <= m; point ++) {
		for(int i = 1; i <= n; i ++) {
			for(int j = 0; j <= (n << 1); j ++) dp[i][j] = dp[i - 1][j]; // 不选
			for(int j = 0; j < (n << 1); j ++) dp[i][j] = (dp[i][j] + dp[i - 1][j + 1] * (pre[i] - a[i][point])) % Mod; // 不选 point 
			for(int j = 1; j <= (n << 1); j ++) dp[i][j] = (dp[i][j] + dp[i - 1][j - 1] * a[i][point]) % Mod; // 选 point 
		}
		for(int j = n + 1; j <= (n << 1); j ++) res = (res + dp[n][j]) % Mod;
	}
	ans -= res; ans = (ans % Mod + Mod) % Mod; printf("%lld", ans);
	return 0;
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值