首先来看一道我编的题:

安娜写宋词

题目背景

洛谷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. 【CSP-S 2019】【洛谷P5664】Emiya 家今天的饭【dp】

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

  5. 洛谷 P5664 [CSP-S2019] Emiya 家今天的饭

    链接: P5664 题意: 给出一个 \(n*m\) 的矩阵 \(a\),选 \(k\) 个格子(\(1\leq k\leq n\)),每行最多选一个,每列最多选\(⌊\dfrac k2⌋\) 个,同 ...

  6. 洛谷P1422 小玉家的电费

    题目描述 夏天到了,各家各户的用电量都增加了许多,相应的电费也交的更多了.小玉家今天收到了一份电费通知单.小玉看到上面写:据闽价电[2006]27号规定,月用电量在150千瓦时及以下部分按每千瓦时0. ...

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

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

  8. CSP2019 Emiya 家今天的饭 题解

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

  9. Emiya 家今天的饭

    \(dp_{i,j,k}\)表示前\(i\)种烹饪方法,假设最多的是食材\(j\),食材\(j\)比其他食材多\(k\)次出现 其中\(i \in [1,n],j \in [1,m],k \in [- ...

随机推荐

  1. Java借助itext pdf生成固定格式pdf的模板工具类

    这里是标题区域 这里是副标题1: 副标题的内容 这里是副标题2: 这里是副标题2的内容 这里是副标题3: 这里是副标题3的内容 序号 表头1 复合表头 表头2 子表头1 子表头2 子表头3 1 居左内 ...

  2. 深入理解PHP类的自动载入机制方法

    第一种情况:文件A.php中内容如下 <?phpclass A{ public function __construct(){ echo 'fff'; }}?> 文件C.php 中内容如下 ...

  3. 30分钟学webpack实战

    阅读目录 一:什么是webpack? 他有什么优点? 二:如何安装和配置 三:理解webpack加载器 四:理解less-loader加载器的使用 五:理解babel-loader加载器的含义 六:了 ...

  4. pip安装依赖与生成依赖

    一.安装依赖 从requirements.txt安装依赖库 pip install -r requirements 当提示权限不够时,前面加上sudo#下面就是一个requirements.txt文件 ...

  5. C位域的初步了解

    以为C中的东西了解的差不多了...今天却是第一次才看到位域这个概念, 闲来无事的时候读起了编程之美,看一个问题的时候有种解答用到了位域, 位域的结构体定义,变量声明和结构体很相似: struct (结 ...

  6. html5之本地数据库

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

  7. JavaScript字符串、数组、对象方法总结

    字符串方法 示例字符串: const str = "hello,kitty,hello,vue "; 一.基本方法 charAt(n) 返回指定索引的字符 charCodeAt(n ...

  8. 关于android SDK安装Failed to fetch URL http://dl-ssl.google.com/android/repository/addons_list-1.xml出错

          最近SDK出问题了,然后在google下载了一个android-sdk-windows.rar,然后点击SDK Manager,结果一直不能刷新API Level,然后就开始在网上找了好多 ...

  9. JAVA-WEB-错误之-'OPTION SQL_SELECT_LIMIT=DEFAULT'

    You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version ...

  10. Java中的TreeMap及红黑树

    TreeMap: http://blog.csdn.net/tobeandnottobe/article/details/7232664 红黑树: http://blog.chinaunix.net/ ...