64 pts

类似 乌龟棋 的思想,由于 \(64pts\) 的 \(m <= 3\), 非常小

我们可以设一个 \(dp\),建立 \(m\) 个维度存下每种物品选了几次:

  • \(f[i][A][B][C]\) 表示前 \(i\) 种烹饪方法,第 \(1 / 2/ 3\) 种主要食材各自选了 \(A, B, C\) 道菜的方案数。

状态转移:根据题意,每种烹饪方法最多选一道菜。

  • 不做菜 \(f[i][A][B][C] += f[i - 1][A][B][C]\)
  • 做 \(1\) 道第一种主要食材的菜 : \(f[i][A][B][C] += f[i - 1][A - 1][B][C] * a_{i, 1}\)
  • 做 \(1\) 道第二种主要食材的菜 : \(f[i][A][B][C] += f[i - 1][A][B - 1][C] * a_{i, 2}\)
  • 做 \(1\) 道第三种主要食材的菜 : \(f[i][A][B][C] += f[i - 1][A][B][C - 1] * a_{i, 3}\)

答案:\(\sum_{A = 0}^{n}\sum_{B = 0}^{n}\sum_{C = 0}^{n}f[n][A][B][C]\ (max(A, B, C) <= \lfloor(A + B + C) / 2\rfloor 且 A + B + C > 0)\)


小优化:发现所有状态只会从$A, B, C <= $ 自己的转移,所以可以用类似背包优化空间的思想,从大到小枚举状态,第一维可以滚动掉。

时间复杂度

最多选 \(n\) 道菜故时间复杂度 \(O(n^{m + 1})\)

#include <cstdio>
#include <iostream>
using namespace std;
const int N = 45, M = 6, P = 998244353;
int n, m, a[N][M];
typedef long long LL;
int f[N][N][N];
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++) scanf("%d", &a[i][j]); f[0][0][0] = 1;
for (int i = 1; i <= n; i++) {
for (int A = i; ~A; A--) {
for (int B = i; ~B; B--) {
for (int C = i; ~C; C--) {
if(A && a[i][1]) f[A][B][C] = (f[A][B][C] + (LL)f[A - 1][B][C] * a[i][1]) % P;
if(B && a[i][2]) f[A][B][C] = (f[A][B][C] + (LL)f[A][B - 1][C] * a[i][2]) % P;
if(m == 3 && C && a[i][3]) f[A][B][C] = (f[A][B][C] + (LL)f[A][B][C - 1] * a[i][3]) % P;
}
}
}
}
int ans = 0;
for (int A = n; ~A; A--) {
for (int B = n; ~B; B--) {
for (int C = n; ~C; C--) {
int s = A + B + C;
if(s > 0 && max(A, max(B, C)) <= s / 2) (ans += f[A][B][C]) %= P;
}
}
}
printf("%d\n", ans);
return 0;
}

84 pts

发现 \(64pts\) 后的 \(m\) 猛增,所以我们的算法一定不能具体记录每种主要食材选了多少了。

我们发现一个方案不合法,有且只会有一个主要食材 $ > $ 总数的一半,所以我们不妨考虑容斥,用所有方案数量 - 不合法数量。


求解所有方案数量

所有方案数量很好求,做一个分组背包即可:

\(f[i][j]\) 表示前 \(i\) 种烹饪方式,做了 \(j\) 道菜的方案数。

状态转移:

  • 第 \(i\) 种烹饪方式不做菜:\(f[i][j] += f[i - 1][j]\)
  • 第 \(i\) 种烹饪方法做 \(1\) 道主要食材是 \(k\) 的菜:\(f[i][j] += f[i - 1][j - 1] * a_{i, k}\)

所有方案数量 $ = \sum_{j = 1}^{n}f[n][j]$

优化

  1. \(i, j\) 以来比它小的 \(i', j'\),第一维滚动掉
  2. 观察第二种放菜的转移:\(f[i - 1][j - 1] * a_{i, 1} + f[i - 1][j - 1] * a_{i, 2} +...+ f[i - 1][j - 1] * a_{i, m} = f[i - 1][j - 1] * (a_{i, 1} + a_{i, 2} + ... + a_{i, m})\)。我们可以 \(O(nm)\) 预处理 \(s_i = a_{i, 1} + a_{i, 2} + ... + a_{i, m}\)。每个状态即可 \(O(1)\) 转移。
这步的时间复杂度

这步有 \(n ^ 2\) 个状态,\(O(1)\) 转移。 所以时间复杂度 \(O(n ^ 2)\)

求解不合法数量

由于刚才我们发现的性质:所有不合法方案中有且只会有一个主要食材 $ > $ 总数的一半,我们称那个主要食材为越界食材,我们设越界食材为 \(c\)。

所以我们不妨先用 \(O(m)\) 枚举 \(c\) 。

那么我们可以把其他食材归结为 符合条件的食材,我们便可以用一个维度来记录它选了多少啦~

设 \(dp[i][j][k]\) 为前 \(i\) 种烹饪方式,第 \(c\) 种(越界食材)选了 \(j\) 道,其他食材选了 \(k\) 道的方案数。

状态转移:

  • 第 \(i\) 种烹饪方法不做菜:\(dp[i][j][k] += dp[i - 1][j][k]\)。\(O(1)\) 转移

  • 选第 \(c\) 种(越界食材):\(dp[i][j][k] += dp[i - 1][j - 1][k] * a_{i, c}\ (j > 0)\) \(O(1)\) 转移

  • 选其他食材:\(dp[i][j][k] += \sum_{u = 1, u != c}^{m}dp[i - 1][j][k - 1] * a_{i, u} (k > 0 )\)。$O(m) $ 转移

对答案的贡献

\(\sum f[n][j][k] (j > k)\)

优化:

  1. 跟之前一样可以滚动掉第一维

  2. 第三种转移最耗时,考虑用求解所有方案数量优化2的思想:\(\sum_{u = 1, u != c}^{m}dp[i - 1][j][k - 1] * a_{i, u} = dp[i - 1][j][k - 1] * (\sum_{u = 1, u != c}^{m}a_{i, u}) = dp[i - 1][j][k - 1] * (s_i - a_{i, c})\) 我们就做到了 \(O(1)\) 转移。

这步的时间复杂度

\(O(m)\) 枚举越界食材后,做一个 \(O(n ^ 3)\) 的 \(dp\)。求解不合法数量的总时间复杂度为 \(O(n ^ 3m)\)。

总时间复杂度:\(O(n ^ 3m)\)

#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
typedef long long LL;
const int N = 105, M = 2005, P = 998244353;
int n, m, a[N][M], f[N], s[N];
int dp[N][N];
/*
dp[i][j] 表示不合法的选了 i 个,剩下的总共选了 j 个的方案数 */
LL ans = 0;
/*
f[i] 表示做了 i 道菜的方案数
*/ void inline add(int &x, LL y) {
x = (x + y) % P;
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++) {
scanf("%d", &a[i][j]);
s[i] = ((LL)s[i] + a[i][j]) % P;
} f[0] = 1;
for (int i = 1; i <= n; i++)
for (int j = i; j; j--)
add(f[j], (LL)f[j - 1] * s[i]); for (int i = 1; i <= n; i++) ans = (ans + f[i]) % P; for (int c = 1; c <= m; c++) {
memset(dp, 0, sizeof dp);
dp[0][0] = 1;
for (int i = 1; i <= n; i++) {
for (int j = i; ~j; j--) {
for (int k = i - j; ~k; k--) {
if(j) add(dp[j][k], (LL)dp[j - 1][k] * a[i][c]);
if(k) add(dp[j][k], (LL)dp[j][k - 1] * (s[i] - a[i][c]));
}
}
} for (int j = 1; j <= n; j++) {
for (int k = 0; k < j; k++) ans = (ans - dp[j][k] + P) % P;
}
}
printf("%lld\n", ans);
return 0;
}

100pts

延续 \(84pts\) 的思想,求解不合法数量的 \(O(n ^ 3m)\) 拖累了我们,我们考虑优化。

我们不关系具体越界食材与其他食材选了多少。只用保证越界食材数 $ > $ 其他食材数数即为不合法状态。


不妨把这两个的差作为一个维度,这样即可让 \(dp\) 状态降一维:

  • \(dp[i][j]\) 表示前 \(i\) 中烹饪方法,越界食材数 $ - $ 其他食材数 为 \(j\) 的方案数。

状态转移:

  • 第 \(i\) 种烹饪方法不选:\(dp[i][j] += dp[i - 1][j]\)
  • 选越界食材 \(c\):\(dp[i][j] += dp[i - 1][j - 1] * a_{i, c}\)
  • 选其他食材:\(dp[i][j] += dp[i - 1][j + 1] * (s_i - a_{i, c})\)。

答案贡献:

\(\sum dp[n][j] (j > 0)\)

总时间复杂度 \(O(n ^ 2m)\) 完美通过本题。

\(Tips\):

  1. 注意做差有可能为负数,我们可以把所有状态加一个 \(+n\) 的偏移量就不会数组越界了。

  2. 不要忘记取模!!

#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
typedef long long LL;
const int N = 105, M = 2005, P = 998244353;
int n, m, a[N][M], f[N], s[N];
int dp[N][N << 1];
/*
dp[i][j] 表示不合法的选了 i 个,剩下的总共选了 j 个的方案数 */
LL ans = 0;
/*
f[i] 表示做了 i 道菜的方案数
*/ void inline add(int &x, LL y) {
x = (x + y) % P;
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++) {
scanf("%d", &a[i][j]);
s[i] = ((LL)s[i] + a[i][j]) % P;
} f[0] = 1;
for (int i = 1; i <= n; i++)
for (int j = i; j; j--)
add(f[j], (LL)f[j - 1] * s[i]); for (int i = 1; i <= n; i++) ans = (ans + f[i]) % P; for (int c = 1; c <= m; c++) {
memset(dp, 0, sizeof dp);
dp[0][n] = 1;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n + i; j++) {
dp[i][j] = (dp[i - 1][j] + (LL)dp[i - 1][j - 1] * a[i][c] + (LL)dp[i - 1][j + 1] * (s[i] - a[i][c])) % P;
}
} for (int j = n + 1; j <= n * 2; j++) ans = (ans - dp[n][j] + P) % P;
}
printf("%lld\n", ans);
return 0;
}

CSP-S 2019 Emiya 家今天的饭的更多相关文章

  1. 洛谷P5664 Emiya 家今天的饭 问题分析

    首先来看一道我编的题: 安娜写宋词 题目背景 洛谷P5664 Emiya 家今天的饭[民间数据] 的简化版本. 题目描述 安娜准备去参加宋词大赛,她一共掌握 \(n\) 个 词牌名 ,并且她的宋词总共 ...

  2. 洛谷P5664 Emiya 家今天的饭 题解 动态规划

    首先来看一道题题: 安娜写宋词 题目背景 洛谷P5664 Emiya 家今天的饭[民间数据] 的简化版本. 题目描述 安娜准备去参加宋词大赛,她一共掌握 \(n\) 个 词牌名 ,并且她的宋词总共有 ...

  3. 【CSP-S 2019】【洛谷P5664】Emiya 家今天的饭【dp】

    题目 题目链接:https://www.luogu.org/problem/P5664 Emiya 是个擅长做菜的高中生,他共掌握 \(n\) 种烹饪方法,且会使用 \(m\) 种主要食材做菜.为了方 ...

  4. 【CSP-S 2019】D2T1 Emiya 家今天的饭

    Description 传送门 Solution 算法1 32pts 爆搜,复杂度\(O((m+1)^n)\) 算法2 84pts 裸的dp,复杂度\(O(n^3m)\) 首先有一个显然的性质要知道: ...

  5. Emiya家今天的饭 NOIP2019 (CSP?) 类DP好题 luoguP5664

    luogu题目传送门! 首先,硬求可行方案数并不现实,因为不好求(去年考场就这么挂的,虽然那时候比现在更蒟). 在硬搞可行方案数不行之后,对题目要求的目标进行转换: 可行方案数 = 总方案数 - 不合 ...

  6. 「CSP-S 2019」Emiya 家今天的饭

    description loj 3211 solution 看到题目中要求每种主要食材至多在一半的菜中被使用,容易想到补集转换. 即\(ans=\)总方案数-存在某一种食材在一半以上的菜中被使用的方案 ...

  7. [CSP-S 2019 Day2]Emiya家今天的饭

    思路: 这种题目就考我们首先想到一个性质.这题其实容易想到:超限的菜最多只有一个,再加上这题有容斥那味,就枚举超限的菜然后dp就做完了. 推式子能力还是不行,要看题解. 式子还需要一个优化,就是废除冗 ...

  8. [CSP-S 2019 day2 T1] Emiya家今天的饭

    题面 题解 不考虑每种食材不超过一半的限制,答案是 减去 1 是去掉一道菜都不做的方案. 显然只可能有一种菜超过一半,于是枚举这种菜,对每个方式做背包即可(记一维状态表示这种菜比别的菜多做了多少份). ...

  9. [CSP-S2019]Emiya 家今天的饭 题解

    CSP-S2 2019 D2T1 很不错的一题DP,通过这道题学到了很多. 身为一个对DP一窍不通的蒟蒻,在考场上还挣扎了1h来推式子,居然还有几次几乎推出正解,然而最后还是只能打个32分的暴搜滚粗 ...

随机推荐

  1. 一:NOSQL

    NOSQL =not only SQL 意即为不仅仅是SQL 传统的关系数据库在处理web2.0网站,特别是超大规模和高并发的社交网络服务类型的web2.0纯动态网站已经显得力不从心,出现了很多难以克 ...

  2. python之路 《六》函数

    ---恢复内容开始--- 为什么要有函数? 当你的老板要你写一个程序 1 def 函数0(): 2 # 如果cpu占用率>90 3 # 发送邮件 4 # 发出警报 5 6 def 函数1(): ...

  3. maven 笔记2

    maven 中央工厂的位置:D:\dubbo\apache-maven-3.2.5\lib D:\dubbo\apache-maven-3.2.5\lib pom-4.0.0.xml reposito ...

  4. Python_爬虫笔记_2018.3.19

    Python_爬虫_笔记 1.前言 1.1爬虫用途: 网站采集.接口采集(地图(jis 热力学 屋里端口/协议).微信.知乎.) 1.2基本流程:网页下载(requests)+网页解析+爬虫调度 网页 ...

  5. C# 9.0新特性详解系列之一:只初始化设置器(init only setter)

    1.背景与动机 自C#1.0版本以来,我们要定义一个不可变数据类型的基本做法就是:先声明字段为readonly,再声明只包含get访问器的属性.例子如下: struct Point { public ...

  6. Java web项目JXl导出excel,(从eclipse上移动到tomact服务器上,之路径更改)

    我用的是jxl导出excel,比较简单,最开始我是固定路径不能选择,很局限,后来改了,而且固定路径当把项目放在服务器上时,路径不可行. 在网上各位大神的帮助成功设置响应头,并且可选保存路径. 1.前端 ...

  7. 如何在Camtasia中对录制视频进行动画编辑

    生活中,我们时时会遇到要剪辑视频不知道哪一款软件比较简单:当我们想要录制电脑屏幕时,网上的方法也总是不奏效.那是否有一款软件可以同时兼备这两种功能呢?今天我给大家推荐的便是一款同时兼备这两种功能的软件 ...

  8. Boom 3D支持的音乐都有什么格式

    Boom 3D作为一款专业的3D环绕音效软件,支持多种音频.视频播放格式,除了常用的MP3.WMA.WAV音频格式外,Boom 3D还支持FLAC.AAC这些比较高级的音频格式.同时,Boom 3D还 ...

  9. JS获取当前日期及 js获取当前时间和一星期前的时间

    var myDate = new Date();     new Date() 代表当前 年 月 日 时 分 秒: myDate.getYear();        //获取当前年份(2位),getY ...

  10. Java基础教程——垃圾回收机制

    垃圾回收机制 Garbage Collection,GC 垃圾回收是Java的重要功能之一. |--堆内存:垃圾回收机制只回收堆内存中对象,不回收数据库连接.IO等物理资源. |--失去使用价值,即为 ...