# NOIP2019_Day2T1_Emiya 家今天的饭

题意:


原题意:

Emiya 是个擅长做菜的高中生,他共掌握 n 种烹饪方法,且会使用 m 种主要食材做菜。为了方便叙述,我们对烹饪方法从 1n 编号,对主要食材从 1m 编号。

Emiya 做的每道菜都将使用恰好一种烹饪方法与恰好一种主要食材。更具体地,Emiya 会做 ai,j 道不同的使用烹饪方法 i 和主要食材 j 的菜 (1in,1jm)

这也意味着 Emiya 总共会做 i=1nj=1mai,j 道不同的菜。

Emiya 今天要准备一桌饭招待 Yazid 和 Rin 这对好朋友,然而三个人对菜的搭配有不同的要求,更具体地,对于一种包含 k 道菜的搭配方案而言:

  • Emiya 不会让大家饿肚子,所以将做至少一道菜,即 k1

  • Rin 希望品尝不同烹饪方法做出的菜,因此她要求每道菜的烹饪方法互不相同

  • Yazid 不希望品尝太多同一食材做出的菜,因此他要求每种主要食材至多在一半的菜(即 k2 道菜)中被使用

这里的 x 为下取整函数,表示不超过 x 的最大整数。

这些要求难不倒 Emiya,但他想知道共有多少种不同的符合要求的搭配方案。两种方案不同,当且仅当存在至少一道菜在一种方案中出现,而不在另一种方案中出现。

Emiya 找到了你,请你帮他计算,你只需要告诉他符合所有要求的搭配方案数对质数 998,244,353 取模的结果。


抽象题意:

给出一个 nm 矩阵,要求从中选出 k 个格子,k1

每行能选一个格子或者不选,每列上选的格子总数不能超过 k 的一半,

同时每个格子内也有方案 ai,j 种,求满足所有要求的总方案数模998,244,353

解:


题解又被我咕掉了——援引Misaka Mikoto巨佬的一篇题解吧

代码:


题解中巨佬用了很聪明的方法处理下表为负的情况——添加hash函数

代码的实现方面进一步进行了空间优化——观察转移方程发现随着 i, k 不断枚举,j 始终是不变的!

并且 f..., j, ... 的更新不会由 f..., j1, ... 的某个状态转移来

这就好办了,我们只用记 fi, 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]=j=1 && jjma[1][j] —— 从前 1 行选格子,使得第 j 种食材数量减其他食材数量为-1 —— 办法是选除了 j 种食材的任意一种,方法加起来即可

至此,第一行的情况被考虑完全,i 从2开始枚举

算完一个 j 之后,注意如何统计答案——答案是由ans-所有不符合条件的情况数

一个情况不符合条件当且仅当选 j 种食材比选其他所有的食材数量多1~n,对应选择 j 种食材 k2+1n

(还要卡卡常)

1、写个快读

2、取模的偷工减料,考虑到 f[i1][h(k)]+a[i][j]f[i1][h(k1)]+f[i1][h(k+1)](sum[i]+moda[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; }

__EOF__

本文作者熹圜
本文链接https://www.cnblogs.com/Xiwon/p/13412288.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   熹圜  阅读(148)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示