洛谷P5664 Emiya 家今天的饭 题解 动态规划
首先来看一道题题:
安娜写宋词
题目背景
洛谷P5664 Emiya 家今天的饭【民间数据】 的简化版本。
题目描述
安娜准备去参加宋词大赛,她一共掌握 \(n\) 个 词牌名 ,并且她的宋词总共有 \(m\) 个不同的 主题 。
为了方便描述,我们对词牌名从 \(1\) ~ \(n\) 编号,对主题从 \(1\) ~ \(m\) 编号。
安娜准备了若干首诗,每首诗都有 恰好一个 词牌名与 恰好一个 主题。
更具体地说,安娜为第 \(i\) 个词牌名第 \(j\) 个主题准备了 \(a_{i,j}\) 首宋词(\(1 \le i \le n, 1 \le j \le m\)),这也意味着安娜总共准备了 \(\sum_{i=1}^n \sum_{j=1}^m a_{i,j}\) 首宋词。
宋词大赛有一些规则:
- 每位选手至少要念一首宋词(这意味着安娜至少要选择一首诗念);
- 同一选手不能选择同样的两首具有相同词牌名的宋词念(这意味着同一词牌名的所有诗当中安娜最多只能选一首念);
- 所念的诗歌要表现主题,所以如果一位选手念了 \(k\) 首宋词,那么至少要有 \(\lfloor \frac{k}2 \rfloor + 1\) 首宋词是同一主题的。
这里的 \(\lfloor x \rfloor\) 为向下取整函数,表示不超过 \(x\) 的最大整数。
这些要求难不倒安娜,但是她想知道共有多少种不同的符合要求的选词方案。两种方案不同,当且仅当存在至少一首宋词在一种方案中出现,而不在另一种方案中出现。
请你帮安娜计算一下,一共有多少符合要求的选词方案。
因为数据量可能会比较大,所以你只需要告诉她方案数对 \(1,000,000,007\) 取模的结果即可。
输入格式
输入的第 1 行包含两个用空格隔开的整数 \(n, m\) 。
第 2 行至第 \(n+1\) 行,每行 \(m\) 个用单个空格隔开的整数,其中第 \(i+1\) 行的 \(m\) 个数依次为 \(a_{i,1}, a_{i,2}, ..., a_{i,m}\) 。
输出格式
仅一行一个整数,表示所求方案数对 \(1,000,000,007\) 取模的结果。
输入输出样例
输入 #1
2 3
1 0 1
0 1 1
输出 #1
5
输入 #2
3 3
1 2 3
4 5 0
6 0 0
输出 #2
299
说明/提示
对于100%的数据,保证 \(1 \le n,m \le 100\) ,\(0 \le a_{i,j} \lt 1,000,000,007\) 。
问题分析
这个问题可以归纳成:
给你一个 \(n\) 行 \(m\) 列的矩阵,每一行你最多可以选一个元素,并且需要保证你所选的 k 个元素有至少 \(\lfloor \frac{k}2 \rfloor + 1\) 个出现在同一列。
那么对于这个问题,肯定只有一列上的元素达到了总数的一半以上。
我们不妨设这一列(选择元素数量超过一半的列)为第 \(c\) 列,那么在确定第 \(c\) 列的情况下,我们设状态 \(f_{i,j,k}\) 表示“前 \(i\) 行选择了 \(j\) 个元素在第 \(c\) 列,选择了 \(k\) 个元素不在第 \(c\) 列”的方案总数。
则可以得到状态转移方程为:
- \(f_{0,0,0} = 1\) ;
- \(f_{i,j,k} = f_{i-1,j,k} + f_{i-1,j-1,k} \times a_{i,j} + f_{i-1,j,k-1} \times (S_i - a_{i,j})\)。
其中,\(a_{i,j}\) 表示第i行第j列的元素个数;\(S_i\) 表示 \(\sum_{j=1}^m s_{i,j}\) ,即第 \(i\) 行所有元素之和。
上述算法需要遍历 \(m\) 列,然后对于每一列,需要遍历 \(i\) , \(j\) , \(k\) ,所以总的时间复杂度为 \(O(m \times n^3)\) 。
优化
其实,对于上述问题,我们并不关心 \(j\) 和 \(k\) 的具体数值是什么,我们关心的是 \(j\) 是不是比 \(k\) 大。
那么我们可以发现我们其实是关心的是 \(j-k\) 的值是不是比 \(0\) 大。
然后,我们同样是枚举每一列 \(c\) ,然后重新定义状态 \(f_{i,j}\) 表示“前 \(i\) 行元素中选择在第 \(c\) 列的元素个数与不在第 \(c\) 列的元素个数之差为 \(j\) ” 的方案总数。
则针对这个新的状态,可以得到状态转移方程为:
- \(f_{0,0} = 1\);
- \(f_{i,j} = f_{i-1,j} + f_{i-1,j-1} \times a_{i,j} + f_{i-1,j+1} \times (S_i - a_{i,j})\)。
其中,\(a_{i,j}\) 表示第i行第j列的元素个数;\(S_i\) 表示 \(\sum_{j=1}^m s_{i,j}\) ,即第 \(i\) 行所有元素之和。
上述算法优化了一维空间,时间复杂度变为 \(O(m \times n^2)\) 。
但是要注意的一点是,在计算 \(f_{i,j}\) 的时候,我们可以发现 \(j\) 的范围是在 \([-i, i]\) 这个区间范围内的,所以我们在开数组的时候开一个 \(n \times 2n\) 的数组 \(f[n][2n]\) ,其中,状态 \(f_{i,j}\) 用 \(f[i][j+n]\) 表示。
实现代码如下:
#include <bits/stdc++.h>
using namespace std;
const long long MOD = 1000000007LL;
const int maxn = 110;
int n, m;
long long a[maxn][maxn], sum[maxn], f[maxn][maxn*2], ans;
void solve_col(int c) {
f[0][n] = 1;
for (int i = 1; i <= n; i ++)
for (int j = n-i; j <= n+i; j ++)
f[i][j] = (f[i-1][j] + f[i-1][j-1] * a[i][c] + f[i-1][j+1] * (sum[i] - a[i][c])) % MOD;
for (int i = 1; i <= n; i ++) ans = (ans + f[n][i+n]) % MOD;
}
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i ++) {
for (int j = 1; j <= m; j ++) {
cin >> a[i][j];
sum[i] += a[i][j];
}
}
for (int i = 1; i <= m; i ++) solve_col(i);
cout << ans << endl;
return 0;
}
洛谷P5664 Emiya 家今天的饭
题目链接:https://www.luogu.org/problem/P5664
题目背景:CSP-S D2T1。
题目描述
Emiya 是个擅长做菜的高中生,他共掌握 \(n\) 种烹饪方法,且会使用 \(m\) 种主要食材做菜。为了方便叙述,我们对烹饪方法从 \(1 \sim n\) 编号,对主要食材从 \(1 \sim m\) 编号。
Emiya 做的每道菜都将使用恰好一种烹饪方法与恰好一种主要食材。更具体地,Emiya 会做 \(a_{i,j}\) 道不同的使用烹饪方法 \(i\) 和主要食材 \(j\) 的菜(\(1 \leq i \leq n, 1 \leq j \leq m\) ),这也意味着 Emiya 总共会做 \(\sum\limits_{i=1}^{n} \sum\limits_{j=1}^{m} a_{i,j}\) 道不同的菜。
Emiya 今天要准备一桌饭招待 Yazid 和 Rin 这对好朋友,然而三个人对菜的搭配有不同的要求,更具体地,对于一种包含 \(k\) 道菜的搭配方案而言:
- Emiya 不会让大家饿肚子,所以将做至少一道菜,即 \(k \geq 1\)
- Rin 希望品尝不同烹饪方法做出的菜,因此她要求每道菜的烹饪方法互不相同
- Yazid 不希望品尝太多同一食材做出的菜,因此他要求每种主要食材至多在一半的菜(即 \(\lfloor \frac{k}{2} \rfloor\) 道菜)中被使用
这里的 \(\lfloor x \rfloor\) 为下取整函数,表示不超过 \(x\) 的最大整数。
这些要求难不倒 Emiya,但他想知道共有多少种不同的符合要求的搭配方案。两种方案不同,当且仅当存在至少一道菜在一种方案中出现,而不在另一种方案中出现。
Emiya 找到了你,请你帮他计算,你只需要告诉他符合所有要求的搭配方案数对质数 \(998,244,353\) 取模的结果。
输入格式
第 1 行两个用单个空格隔开的整数 \(n,m\) 。
第 2 行至第 \(n + 1\) 行,每行 \(m\) 个用单个空格隔开的整数,其中第 \(i + 1\) 行的 \(m\) 个数依次为 \(a_{i,1}, a_{i,2}, \cdots, a_{i,m}\) 。
输出格式
仅一行一个整数,表示所求方案数对 \(998,244,353\) 取模的结果。
输入输出样例
输入 #1
2 3
1 0 1
0 1 1
输出 #1
3
输入 #2
3 3
1 2 3
4 5 0
6 0 0
输出 #2
190
输入 #3
5 5
1 0 0 1 1
0 1 0 1 0
1 1 1 1 0
1 0 1 0 1
0 1 1 0 1
输出 #3
742
说明/提示
【样例 1 解释】
由于在这个样例中,对于每组 i, ji,j,Emiya 都最多只会做一道菜,因此我们直接通过给出烹饪方法、主要食材的编号来描述一道菜。
符合要求的方案包括:
- 做一道用烹饪方法 1、主要食材 1 的菜和一道用烹饪方法 2、主要食材 2 的菜
- 做一道用烹饪方法 1、主要食材 1 的菜和一道用烹饪方法 2、主要食材 3 的菜
- 做一道用烹饪方法 1、主要食材 3 的菜和一道用烹饪方法 2、主要食材 2 的菜
因此输出结果为 \(3 \mod 998,244,353 = 3\) 。 需要注意的是,所有只包含一道菜的方案都是不符合要求的,因为唯一的主要食材在超过一半的菜中出现,这不满足 Yazid 的要求。
【样例 2 解释】
Emiya 必须至少做 2 道菜。
- 做 2 道菜的符合要求的方案数为 100。
- 做 3 道菜的符合要求的方案数为 90。
因此符合要求的方案数为 100 + 90 = 190。
【数据范围】
对于所有测试点,保证 \(1 \leq n \leq 100\),\(1 \leq m \leq 2000\),\(0 \leq a_{i,j} \lt 998,244,353\) 。
问题分析
题解转载自Caro23333大神的博客:https://www.luogu.org/blog/Caro23333/solution-p5664
这个题作为d2t1比往年偏难,但完全在可以接受和预见的范围。
首先考虑列的限制,发现若有不合法的列,则必然有且只有一列是不合法的:因为不可能有不同的两列数量都超过总数的一半。
于是发现列的限制容易容斥计算:每行选不超过一个的方案数 - 每行选不超过一个,且某一列选了超过一半的方案数。
那么考虑枚举不合法的一列。假设我们已经枚举了不合法的列为 \(col\) ,接下来会发现我们只关心一个数的位置是否在当前列;如果属于在其他列的情况,那么它具体在哪一列对当前列的合法性并无影响,我们并不需要考虑。
接下来设计状态。
\(f_{i,j,k}\) 表示对于 \(col\) 这一列,前 \(i\) 行在 \(col\) 列中选了 \(j\) 个,在其他列中选了 \(k\) 个,那么令 \(s_i\) 为第 \(i\) 行的总和,则有转移:
\]
状态数 \(O(n^3)\) ,转移 \(O(1)\) ,算上枚举 \(col\) ,这一步复杂度是 \(O(mn^3)\) 的。统计如下和式的值并对每一列求和即可得到不合法的方案数:
\]
接下来考虑计算总方案数:和之前相似,只需设 \(g_{i,j}\) 为前 \(i\) 行共选了 \(j\) 个数的方案数,则有转移:
\]
那么 \(\sum\limits_{i=1}^n g_{n,i}\) 就是总方案数, 这一步是 \(O(n^2)\) 的。所以现在可以在 \(O(mn^3)\) 的总复杂度内完成这题,获得84分。
考虑进一步优化,剪去无用状态:注意到在不合法情况的计算过程中,也就是 \(f_{i,j,k}\) 的转移过程中,我们实际上并不关心 \(j,k\) 的具体数值,而只关心相对的大小关系;所以我们可以将状态变为 \(f_{i,j}\) ,表示前 \(i\) 行,当前列的数比其他列的数多了 \(j\) 个,则有转移:
\]
转移仍然是 \(O(1)\) 的,但总复杂度降为 \(O(mn^2)\) ,可以通过此题。
实现代码如下:
#include <bits/stdc++.h>
using namespace std;
const long long MOD = 998244353LL;
const int maxn = 101, maxm = 2002;
int n, m;
long long a[maxn][maxm], sum[maxn], ans, f[maxn][maxn<<1], g[maxn];
void solve_tot() {
ans = 1;
for (int i = 1; i <= n; i ++)
ans = ans * (sum[i] + 1) % MOD;
ans = (ans - 1 + MOD) % MOD;
}
void solve_col(int col) {
f[0][n] = 1;
for (int i = 1; i <= n; i ++) {
for (int j = n-i; j <= n+i; j ++) {
f[i][j] = ( f[i-1][j] + f[i-1][j-1] * a[i][col] + f[i-1][j+1] * (sum[i] - a[i][col]) ) % MOD;
}
}
long long tmp = 0;
for (int i = 1; i <= n; i ++) tmp = (tmp + f[n][i+n]) % MOD;
ans = (ans - tmp + MOD) % MOD;
}
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i ++) {
for (int j = 1; j <= m; j ++) {
cin >> a[i][j];
sum[i] = (sum[i] + a[i][j]) % MOD;
}
}
solve_tot();
for (int i = 1; i <= m; i ++) solve_col(i);
cout << ans << endl;
return 0;
}
代码说明:
solve_tot()
函数用来计算所有方案数;solve_col(int col)
函数用来计算针对第 \(col\) 列所有不满足要求的方案(并减去)。
洛谷P5664 Emiya 家今天的饭 题解 动态规划的更多相关文章
- 洛谷P5664 Emiya 家今天的饭 问题分析
首先来看一道我编的题: 安娜写宋词 题目背景 洛谷P5664 Emiya 家今天的饭[民间数据] 的简化版本. 题目描述 安娜准备去参加宋词大赛,她一共掌握 \(n\) 个 词牌名 ,并且她的宋词总共 ...
- 洛谷 P5664 Emiya 家今天的饭(84分)
题目传送门 解题思路: 对于每一个列c,f[i][j][k]表示到第i行,第c列选了j个,其它列一共选了k个,然后我们读题意发现只要j>k,那就一定是不合法的,然后统计所有方案,减去所有不合法方 ...
- P5664 Emiya 家今天的饭
题面 link 前言 去年把我做自闭的一道题,看了一眼题面,发现只有 t1 有点思路,结果写到一半发现自己读错题了,又只能花时间来重构,结果后面的暴力一点都没写(主要是自己当时不会) 然后,这道题还因 ...
- CSP2019 Emiya 家今天的饭 题解
这题在考场上只会O(n^3 m),拿了84分.. 先讲84分,考虑容斥,用总方案减去不合法方案,也就是枚举每一种食材,求用它做超过\(\lfloor \frac{k}{2} \rfloor\) 道菜的 ...
- csp2019 Emiya家今天的饭题解
qwq 由于窝太菜了,实在是不会,所以在题解的帮助下过掉了这道题. 写此博客来整理一下思路 正文 传送 简化一下题意:现在有\(n\)行\(m\)列数,选\(k\)个数的合法方案需满足: 1.一行最多 ...
- [CSP-S2019]Emiya 家今天的饭 题解
CSP-S2 2019 D2T1 很不错的一题DP,通过这道题学到了很多. 身为一个对DP一窍不通的蒟蒻,在考场上还挣扎了1h来推式子,居然还有几次几乎推出正解,然而最后还是只能打个32分的暴搜滚粗 ...
- 洛谷P1484 种树&洛谷P3620 [APIO/CTSC 2007]数据备份 题解(堆+贪心)
洛谷P1484 种树&洛谷P3620 [APIO/CTSC 2007]数据备份 题解(堆+贪心) 标签:题解 阅读体验:https://zybuluo.com/Junlier/note/132 ...
- 【CSP-S 2019】【洛谷P5664】Emiya 家今天的饭【dp】
题目 题目链接:https://www.luogu.org/problem/P5664 Emiya 是个擅长做菜的高中生,他共掌握 \(n\) 种烹饪方法,且会使用 \(m\) 种主要食材做菜.为了方 ...
- 洛谷AT2342 Train Service Planning(思维,动态规划,珂朵莉树)
洛谷题目传送门 神仙思维题还是要写点东西才好. 建立数学模型 这种很抽象的东西没有式子描述一下显然是下不了手的. 因为任何位置都以\(k\)为周期,所以我们只用关心一个周期,也就是以下数都在膜\(k\ ...
随机推荐
- Error While Loading Shared Libraries, Cannot Open Shared Object File
In the "I wish the Internet had an actual correct answer" category comes a question from a ...
- Android的headerView和emptyView共存问题
今天做项目的时候,准备优化下ListView相关的东西,于是乎,需要做一个当列表无数据时,空的提醒页面.这个自然想到的是ListView的setEmptyView()方法,于是顺手就写了,可是,当我为 ...
- 割点(tarjan)
对于根来说,如果它有超过1棵子树,那么它是一个割点 对于非叶结点来说,如果它的某一个儿子没有回边能到达高于它的点,那么它是一个割点 叶节点不是割点 //洛谷3388 #include<algor ...
- 一个简单的hexo搭建博客网站的故事
首先安装hexo mkdir hexo #创建一个文件夹 cd hexo #切换到hexo目录下面 npm install -g hexo-cli npm install hexo --save 然后 ...
- elasticsearch-倒排索引原理
倒排索引 Elasticsearch 使用一种称为 倒排索引 的结构,它适用于快速的全文搜索.一个倒排索引由文档中所有不重复词的列表构成,对于其中每个词,有一个包含它的文档列表. 例如,假设我们有两个 ...
- 数据存储在哪里? Java是值传递还是引用传递?
寄存器 : 最快的存储区,位于处理器中,寄存器会按需求自行分配空间,java不能控制寄存器,所以在程序中感觉不到它的存在 栈(stack) : 位于RAM(内存)中,速度仅次于寄存器,存储对象的引用( ...
- 4-10 items设计
1,items相当于dict,但是又比字典好 2,parse.urljoin(response.url,post_url)方法,其中image_url是一个域名的话,其中的当前域名就不用再添加. yi ...
- Codeforces Round #181 (Div. 2)
A. Array 模拟. B. Coach 模拟. C. Beautiful Numbers good number的位和最大不超过\(10^7\),那么只要枚举a或b的个数,然后最多循环7次判断位和 ...
- H5 数据存储localStorage
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- 【u104】组合数
Time Limit: 1 second Memory Limit: 128 MB [问题描述] 组合数C(N, K)表示了N个数字不重复地选取K个作组合的方案数. C(N, K) = N!/(N-M ...