首先来看一道我编的题:

安娜写宋词

题目背景

洛谷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. PHP配置环境中开启GD库

    下配置好的PHP环境中,GD库不像windows那样可以直接用,而是默认关闭,需要把它打开,去到php.ini文件中 找到php_gd2.dll把分号去掉即可.(注:GD库跟绘制二维码等有关)

  2. SSH 相关基础

    检查是否安装: sudo apt-cache policy openssh-client sudo apt-cache policy  openssh-server 也可直接用 sudo apt-ca ...

  3. OpenLayers图层

    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <html> <head ...

  4. 【水滴石穿】LoginScreen_Firabase_ReactNativeApp_Redux

    先看效果 分析代码我们会发现,它使用了firebase 关于登陆部分应该是实时数据库,应该是他们后端校验的 //app.js //这里使用了实时数据库 import React, { Componen ...

  5. SQL SERVER 2008 R2 插入数据非常慢

    表是5字段int类型,第一个字段是主健,自增字段 表结构: id int  Uncheckedbillno bigint  Uncheckedopid int  Checkedbillopid int ...

  6. 写一个杀死Gradle Daemon的shell脚本和bat脚本

    1. Gradle Daemon也就是Gradle守护进程 Gradle需要运行在一个Java虚拟机中,每一次执行gradle命令就意味着一个新的Java虚拟机被启动,然后加载Gradle类和库,最后 ...

  7. 【JZOJ4742】【NOIP2016提高A组模拟9.2】单峰

    题目描述 输入 输出 样例输入 2 样例输出 2 数据范围 解法 答案为2^(n-1),快速幂即可. 证明:显然峰值必定为n,那么对于其他n-1个数,要么放在峰值的左边,要么放在峰值的右边,所以方案数 ...

  8. nginx与apache

    参考链接:https://www.cnblogs.com/changning0822/p/7844004.html

  9. Java中的TreeMap及红黑树

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

  10. 对象无法注册到Spring容器中,手动从spring容器中拿到我们需要的对象

    当前对象没有注册到spring容器中,此时无法new object()  的方式创建对象,否则所有@Autowired 注入的对象都为null; 处理方式: 手动创建一个类@Component注册到S ...