题面

link

前言

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

然后,这道题还因为某种原因爆玲了,因此我就成了全机房最菜的人。

题解

这道题题面还是很长的,所以我们简化一下题意。

给你一个 n*m 的矩阵,要求你从每一行选一个数,这一行可以选也可以不选,但最后至少选一个,且选的最多的那一列不能超过选的总数的 \(1 \over 2\)

part 1 24 - 32 分

直接爆搜出结果,加上一些剪枝可以拿到一部分了。

part 2 84 分

容斥加 dp。

没有第三个限制我们其实很好求,但带上第三个却有些麻烦。

我们考虑容斥一下,用总的方案数减去不合法的方案数,就是最后答案。

总的方案数就是 \((\displaystyle\prod_{i=1}^{n}\sum_{j=1}^{m} a[i][j]+1)\) -1

解释一下,每一行可以分开来考虑,乘法计数原理,对于这一行可以用加法计数原理,也就是这一行所有的树相加在加一,加一是因为要算上这一行不选的情况。

最后在减一,除去所有行都不选的情况。

对于不合法的方案数,可以考虑是哪一行选多了不合法,枚举每一行不合法的方案数,最后再总和就是不合法的方案数。

我们可以考虑用 dp 来解决这个问题。

设 \(f[i][j][k]\) 表示前 \(i\) 行,选了 \(j\) 列,且现在枚举的这一列选了 \(j\) 个的方案数。

转移就是 f[i][j][k] = f[i-1][j][k] (不选的时候) + f[i-1][j-1][k-1] * a[i][u] (选这一列的时候) + f[i-1][j-1][k] * (sum[i]-a[i][u])(选其他列的时候)

最后在减去不合法的方案数就是最后答案。

  1. #include<iostream>
  2. #include<cstdio>
  3. #include<algorithm>
  4. #include<cstring>
  5. using namespace std;
  6. #define int long long
  7. const int p = 998244353;
  8. int n,m,tot,a[110][2010],sum[110],f[110][110][110];
  9. inline int read()
  10. {
  11. int s = 0, w = 1; char ch = getchar();
  12. while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
  13. while(ch >= '0' && ch <= '9'){s =s * 10+ch - '0'; ch = getchar();}
  14. return s * w;
  15. }
  16. void calc(int id)//计算不合法的情况
  17. {
  18. memset(f,0,sizeof(f));
  19. f[0][0][0] = 1;
  20. for(int i = 1; i <= n; i++)
  21. {
  22. f[i][0][0] = 1;
  23. for(int j = 1; j <= i; j++)
  24. {
  25. for(int k = 0; k <= j; k++)
  26. {
  27. f[i][j][k] = (f[i-1][j][k] + f[i-1][j-1][k-1] * a[i][id] % p) % p;
  28. f[i][j][k] = (f[i][j][k] + (f[i-1][j-1][k] * (sum[i]-a[i][id]) % p)) % p;
  29. }
  30. }
  31. }
  32. // for(int i = 1; i <= n; i++) for(int j = 1; j <= i; j++) cout<<f[n][i][j]<<endl;
  33. }
  34. signed main()
  35. {
  36. n = read(); m = read(); tot = 1;
  37. for(int i = 1; i <= n; i++)
  38. {
  39. for(int j = 1; j <= m; j++)
  40. {
  41. a[i][j] = read();
  42. sum[i] = (sum[i] + a[i][j])%p;
  43. }
  44. tot = (tot * (sum[i]+1))%p;
  45. }
  46. tot -= 1;//总方案数
  47. for(int i = 1; i <= m; i++)
  48. {
  49. calc(i);
  50. for(int j = 1; j <= n; j++)
  51. {
  52. for(int k = j/2+1; k <= j; k++)//枚举选了多少行,以及不合法的情况
  53. {
  54. tot = (tot - f[n][j][k])%p;
  55. }
  56. }
  57. }
  58. printf("%lld\n",(tot%p+p)%p);
  59. return 0;
  60. }

part 3 100 分

你会发现上面会跑的很慢,因为他的复杂度是 O(\(nm^3\)) 的,我们只能想办法优化掉一个 \(m\)

然后,这就是本题最关键也是最巧妙的地方,我们不用管心 \(j\) 与 \(k\) 到底具体选了多少个,

而是关心 \(k > {j \over 2}\) 即 \(2 \times k < j\) ,所以我们可以把第二维和第三维合并成一维,用 \(2 \times k -j\) 的差值来表示(你也可以用 \(k - j\) 来表示)。

转移就是 f[i][j] = f[i-1][j](不选的话差值不变) + f[i-1][j-1] * a[i][k] (选这一列的时候) + f[i-1][j+1] * (sim[i]-a[i][k]) (选其他列的情况)

由于他可能会出现负数,所以我们整体平移 \(n\) 表示 \(2 \times k - j + n\) 的差值就避免了 RE 的问题。

Code
  1. #include<iostream>
  2. #include<cstdio>
  3. #include<algorithm>
  4. #include<cstring>
  5. using namespace std;
  6. #define int long long
  7. const int p = 998244353;
  8. int n,m,tot,a[110][2010],sum[110],f[110][220];
  9. inline int read()
  10. {
  11. int s = 0, w = 1; char ch = getchar();
  12. while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
  13. while(ch >= '0' && ch <= '9'){s =s * 10+ch - '0'; ch = getchar();}
  14. return s * w;
  15. }
  16. void calc(int id)
  17. {
  18. memset(f,0,sizeof(f));
  19. f[0][n] = 1;
  20. for(int i = 1; i <= n; i++)
  21. {
  22. f[i][n] = 1;
  23. for(int j = 0; j <= 2 * n; j++)
  24. {
  25. f[i][j] = (f[i-1][j] + f[i-1][j-1] * a[i][id] % p) % p;
  26. f[i][j] = (f[i][j] + (f[i-1][j+1] * (sum[i]-a[i][id]) % p)) % p;
  27. }
  28. }
  29. // for(int i = 1; i <= n; i++) for(int j = 1; j <= i; j++) cout<<f[n][i][j]<<endl;
  30. }
  31. signed main()
  32. {
  33. n = read(); m = read(); tot = 1;
  34. for(int i = 1; i <= n; i++)
  35. {
  36. for(int j = 1; j <= m; j++)
  37. {
  38. a[i][j] = read();
  39. sum[i] = (sum[i] + a[i][j])%p;
  40. }
  41. tot = (tot * (sum[i]+1))%p;
  42. }
  43. tot -= 1;
  44. for(int i = 1; i <= m; i++)
  45. {
  46. calc(i);
  47. for(int j = 1; j <= n; j++)
  48. {
  49. tot = (tot - f[n][n+j])%p;
  50. }
  51. }
  52. printf("%lld\n",(tot%p+p)%p);
  53. return 0;
  54. }

P5664 Emiya 家今天的饭的更多相关文章

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

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

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

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

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

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

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

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

  7. CSP2019 Emiya 家今天的饭 题解

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

  8. Emiya 家今天的饭

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

  9. 【NOIP/CSP2019】D2T1 Emiya 家今天的饭

    这个D2T1有点难度啊 原题: 花了我一下午的时间,作为D2T1的确反常 条件很奇怪,感觉不太直观,于是看数据范围先写了个暴力 写暴力的时候我就注意到了之前没有仔细想过的点,烹饪方式必须不同 虽然a很 ...

随机推荐

  1. 【Android】listview 嵌套gridview报错,代码:”during second layout pass: posting in next frame

    作者:程序员小冰,CSDN博客:http://blog.csdn.net/qq_21376985, QQ986945193 公众号:程序员小冰 说明:本人曾经在listview嵌套gridview出现 ...

  2. 【HttpRunner v3.x】笔记 ——1. 环境安装

    一.环境说明 HttpRunner 是一个基于 Python 开发的测试框架,可以运行在 macOS.Linux.Windows 系统平台上.笔者使用的是windows系统,所以后续都是基于windo ...

  3. MPI Maelstrom (Dijstra+atoi函数转换整数)

    BIT has recently taken delivery of their new supercomputer, a 32 processor Apollo Odyssey distribute ...

  4. Tomcat之如何自己做一个tomcat

    来源:<How Tomcat Works> Servlet容器的工作原理: 1.创建一个request对象并填充那些有可能被所引用的servlet使用的信息,比如参数.头部.cookies ...

  5. map 地图组件

    地图选择器网址 http://datav.aliyun.com/tools/atlas/#&lat=31.769817845138945&lng=104.29901249999999& ...

  6. Medium

    https://www.medium.com 破解阅读限制 https://medium-unlimited.ml/download/

  7. 在Windows上安装PHP(将PHP加载到Apache中)

    第一步:在 windows.php.net 下载软件包 第二步:解压压缩包,将解压后的目录放到指定目录并重命名 第三步: 创建PHP配置文件,修改Apache配置文件(httpd.conf),将PHP ...

  8. CSS -- 盒子模型之边框、内边距、外边距

    一.使用border为盒子添加边框 盒子模型的边框就是围绕着内容及补白的线,这条线你可以设置它的粗细.样式和颜色(边框三个属性). 1.border-style(边框样式)常见样式有: dashed( ...

  9. Redis Cluster集群架构实现

    Redis集群简介 通过前面三篇博客的介绍<Redis基础认识及常用命令使用(一)–技术流ken>,<Redis基础知识补充及持久化.备份介绍(二)–技术流ken>,<R ...

  10. Mock简明文档

    Mock简明文档 Mock.mock() Mock.mock( requestUrl?, requestType?, template|function(options) ) Mock.mock( t ...