首先来看一道题题:

安娜写宋词

题目背景

洛谷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}\) 首宋词。

宋词大赛有一些规则:

  1. 每位选手至少要念一首宋词(这意味着安娜至少要选择一首诗念);
  2. 同一选手不能选择同样的两首具有相同词牌名的宋词念(这意味着同一词牌名的所有诗当中安娜最多只能选一首念);
  3. 所念的诗歌要表现主题,所以如果一位选手念了 \(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\) 行的总和,则有转移:

\[f_{i,j,k} = f_{i-1,j,k}\ +\ a_{i,col}* f_{i-1,j-1,k}\ +\ (s_i-a_{i,col})* f_{i-1,j,k-1}
\]

状态数 \(O(n^3)\) ,转移 \(O(1)\) ,算上枚举 \(col\) ,这一步复杂度是 \(O(mn^3)\) 的。统计如下和式的值并对每一列求和即可得到不合法的方案数:

\[\sum_{j>k} f_{n,j,k}
\]

接下来考虑计算总方案数:和之前相似,只需设 \(g_{i,j}\) 为前 \(i\) 行共选了 \(j\) 个数的方案数,则有转移:

\[g_{i,j} = g_{i-1,j}\ +\ s_i*g_{i-1,j-1}
\]

那么 \(\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\) 个,则有转移:

\[f_{i,j} = f_{i-1,j}\ +\ a_{i,col}* f_{i-1,j-1}\ +\ (s_i-a_{i,col})* f_{i-1,j+1}
\]

转移仍然是 \(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 家今天的饭 题解 动态规划的更多相关文章

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

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

  2. 洛谷 P5664 Emiya 家今天的饭(84分)

    题目传送门 解题思路: 对于每一个列c,f[i][j][k]表示到第i行,第c列选了j个,其它列一共选了k个,然后我们读题意发现只要j>k,那就一定是不合法的,然后统计所有方案,减去所有不合法方 ...

  3. P5664 Emiya 家今天的饭

    题面 link 前言 去年把我做自闭的一道题,看了一眼题面,发现只有 t1 有点思路,结果写到一半发现自己读错题了,又只能花时间来重构,结果后面的暴力一点都没写(主要是自己当时不会) 然后,这道题还因 ...

  4. CSP2019 Emiya 家今天的饭 题解

    这题在考场上只会O(n^3 m),拿了84分.. 先讲84分,考虑容斥,用总方案减去不合法方案,也就是枚举每一种食材,求用它做超过\(\lfloor \frac{k}{2} \rfloor\) 道菜的 ...

  5. csp2019 Emiya家今天的饭题解

    qwq 由于窝太菜了,实在是不会,所以在题解的帮助下过掉了这道题. 写此博客来整理一下思路 正文 传送 简化一下题意:现在有\(n\)行\(m\)列数,选\(k\)个数的合法方案需满足: 1.一行最多 ...

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

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

  7. 洛谷P1484 种树&洛谷P3620 [APIO/CTSC 2007]数据备份 题解(堆+贪心)

    洛谷P1484 种树&洛谷P3620 [APIO/CTSC 2007]数据备份 题解(堆+贪心) 标签:题解 阅读体验:https://zybuluo.com/Junlier/note/132 ...

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

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

  9. 洛谷AT2342 Train Service Planning(思维,动态规划,珂朵莉树)

    洛谷题目传送门 神仙思维题还是要写点东西才好. 建立数学模型 这种很抽象的东西没有式子描述一下显然是下不了手的. 因为任何位置都以\(k\)为周期,所以我们只用关心一个周期,也就是以下数都在膜\(k\ ...

随机推荐

  1. 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 ...

  2. Android的headerView和emptyView共存问题

    今天做项目的时候,准备优化下ListView相关的东西,于是乎,需要做一个当列表无数据时,空的提醒页面.这个自然想到的是ListView的setEmptyView()方法,于是顺手就写了,可是,当我为 ...

  3. 割点(tarjan)

    对于根来说,如果它有超过1棵子树,那么它是一个割点 对于非叶结点来说,如果它的某一个儿子没有回边能到达高于它的点,那么它是一个割点 叶节点不是割点 //洛谷3388 #include<algor ...

  4. 一个简单的hexo搭建博客网站的故事

    首先安装hexo mkdir hexo #创建一个文件夹 cd hexo #切换到hexo目录下面 npm install -g hexo-cli npm install hexo --save 然后 ...

  5. elasticsearch-倒排索引原理

    倒排索引 Elasticsearch 使用一种称为 倒排索引 的结构,它适用于快速的全文搜索.一个倒排索引由文档中所有不重复词的列表构成,对于其中每个词,有一个包含它的文档列表. 例如,假设我们有两个 ...

  6. 数据存储在哪里? Java是值传递还是引用传递?

    寄存器 : 最快的存储区,位于处理器中,寄存器会按需求自行分配空间,java不能控制寄存器,所以在程序中感觉不到它的存在 栈(stack) : 位于RAM(内存)中,速度仅次于寄存器,存储对象的引用( ...

  7. 4-10 items设计

    1,items相当于dict,但是又比字典好 2,parse.urljoin(response.url,post_url)方法,其中image_url是一个域名的话,其中的当前域名就不用再添加. yi ...

  8. Codeforces Round #181 (Div. 2)

    A. Array 模拟. B. Coach 模拟. C. Beautiful Numbers good number的位和最大不超过\(10^7\),那么只要枚举a或b的个数,然后最多循环7次判断位和 ...

  9. H5 数据存储localStorage

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  10. 【u104】组合数

    Time Limit: 1 second Memory Limit: 128 MB [问题描述] 组合数C(N, K)表示了N个数字不重复地选取K个作组合的方案数. C(N, K) = N!/(N-M ...