# NOIP2019_Day2T1_Emiya 家今天的饭
题意:
原题意:
Emiya 是个擅长做菜的高中生,他共掌握 \(n\) 种烹饪方法,且会使用 \(m\) 种主要食材做菜。为了方便叙述,我们对烹饪方法从 \(1∼n\) 编号,对主要食材从 \(1∼m\) 编号。
Emiya 做的每道菜都将使用恰好一种烹饪方法与恰好一种主要食材。更具体地,Emiya 会做 \(a_{i,j}\) 道不同的使用烹饪方法 \(i\) 和主要食材 \(j\) 的菜 \((1≤i≤n,1≤j≤m)\),
这也意味着 Emiya 总共会做 \(\sum\limits_{i=1}^{n} \sum\limits_{j=1}^{m} a_{i,j}\) 道不同的菜。
Emiya 今天要准备一桌饭招待 Yazid 和 Rin 这对好朋友,然而三个人对菜的搭配有不同的要求,更具体地,对于一种包含 \(k\) 道菜的搭配方案而言:
-
Emiya 不会让大家饿肚子,所以将做至少一道菜,即 \(k≥1\)
-
Rin 希望品尝不同烹饪方法做出的菜,因此她要求每道菜的烹饪方法互不相同
-
Yazid 不希望品尝太多同一食材做出的菜,因此他要求每种主要食材至多在一半的菜(即 \(\lfloor \frac{k}{2} \rfloor\) 道菜)中被使用
这里的 \(\lfloor x \rfloor\) 为下取整函数,表示不超过 \(x\) 的最大整数。
这些要求难不倒 Emiya,但他想知道共有多少种不同的符合要求的搭配方案。两种方案不同,当且仅当存在至少一道菜在一种方案中出现,而不在另一种方案中出现。
Emiya 找到了你,请你帮他计算,你只需要告诉他符合所有要求的搭配方案数对质数 998,244,353 取模的结果。
抽象题意:
给出一个 \(n*m\) 矩阵,要求从中选出 \(k\) 个格子,\(k≥1\)
每行能选一个格子或者不选,每列上选的格子总数不能超过 \(k\) 的一半,
同时每个格子内也有方案 \(a_{i,j}\) 种,求满足所有要求的总方案数模998,244,353
解:
题解又被我咕掉了——援引Misaka Mikoto巨佬的一篇题解吧
代码:
题解中巨佬用了很聪明的方法处理下表为负的情况——添加hash函数
代码的实现方面进一步进行了空间优化——观察转移方程发现随着 \(i,\ k\) 不断枚举,\(j\) 始终是不变的!
并且 \(f_{...,\ j,\ ...}\) 的更新不会由 \(f_{...,\ j-1,\ ...}\) 的某个状态转移来
这就好办了,我们只用记 \(f_{i,\ k}\) ,外层的 \(j\) 让它不断枚举,并把它看作不变的量
但是要注意,从计算完一个 \(j\) 该计算 \(j+1\) 的时候,f数组要清空
还有初始化:
ans最开始初始化为所有方案数,记得sum[i]还要加一,对应不选的情况,ans = (ans * (sum[i] + 1)) % mod;
\(f[1][0]=1\) —— 从前 \(1\) 行选格子,使得第 \(j\) 种食材数量减其他食材数量为0 —— 唯一的办法是第一行什么也不选
\(f[1][1]=a[1][j]\) —— 从前 \(1\) 行选格子,使得第 \(j\) 种食材数量减其他食材数量为1 —— 只能是选上第 \(j\) 种食材的一道菜,其中有 \(a[1][j]\) 种选法
\(f[1][-1]=\sum_{j'=1\ \&\&\ j' \ne j}^{m}a[1][j']\) —— 从前 \(1\) 行选格子,使得第 \(j\) 种食材数量减其他食材数量为-1 —— 办法是选除了 \(j\) 种食材的任意一种,方法加起来即可
至此,第一行的情况被考虑完全,\(i\) 从2开始枚举
算完一个 \(j\) 之后,注意如何统计答案——答案是由ans-所有不符合条件的情况数
一个情况不符合条件当且仅当选 \(j\) 种食材比选其他所有的食材数量多1~n,对应选择 \(j\) 种食材 \(\lfloor \frac{k}{2} \rfloor+1 ∼ n\) 次
(还要卡卡常)
1、写个快读
2、取模的偷工减料,考虑到 \(f[i-1][h(k)]+a[i][j]*f[i-1][h(k-1)]+f[i-1][h(k+1)]*(sum[i]+mod-a[i][j])\) 每一项最大都为998244353-1,
所以这个式子最大值大概为1,992,983,578,589,265,924,比ll的最大范围小,不会溢出,所以中间的取模都省了,最后只用模一次
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod = 998244353;
int n, m;
ll a[110][2010];
ll sum[110];
ll f[110][2010];
ll ans;
ll read() {
ll xx = 0; char ch = getchar();
while (ch < '0'|| ch > '9') ch = getchar();
while (ch >= '0' && ch <= '9') xx = (xx << 1) + (xx << 3) + ch - '0', ch = getchar();
return xx;
}
inline int h(int x) { return x + n + 5; }
int main() {
n = read(); m = read();
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++) {
a[i][j] = read();
sum[i] = (sum[i] + a[i][j]) % mod;
}
ans = 1;
for (int i = 1; i <= n; i++)
ans = (ans * (sum[i] + 1)) % mod;
ans--;
for (int j = 1; j <= m; j++) {
memset(f, 0, sizeof f);
f[1][h(0)] = 1;
f[1][h(1)] = a[1][j];
f[1][h(-1)] = (sum[1] + mod - a[1][j]) % mod;
for (int i = 2; i <= n; i++)
for (int k = -n; k <= n; k++)
f[i][h(k)] = ( (f[i - 1][h(k)] + a[i][j] * f[i - 1][h(k - 1)])
+ (f[i - 1][h(k + 1)] * (sum[i] + mod - a[i][j])) ) % mod;
for (int k = 1; k <= n; k++)
ans = (ans + mod - f[n][h(k)]) % mod;
}
printf("%lld\n", ans);
return 0;
}