洛谷 P5664 [CSP-S2019] Emiya 家今天的饭
链接:
题意:
给出一个 \(n*m\) 的矩阵 \(a\),选 \(k\) 个格子(\(1\leq k\leq n\)),每行最多选一个,每列最多选\(⌊\dfrac k2⌋\) 个,同时每个格子有 \(a_{i,j}\) 种不同选法,问共有多少种不同的选法,对 \(998244353\) 取模。给出 \(n,m\) 和 矩阵 \(a\)。
分析:
尝试直接 dp 失败后看了题解。这是道 dp 和容斥的好题。
考虑列的限制,每列最多选\(⌊\dfrac k2⌋\) 个,意味着最多只有一列不合法,所以我们可以枚举不合法的列,似乎能更容易地算出不合法的方案数。
所以考虑算出不计列限制的方案数,减去列限制不合法的方案数来容斥得到答案。
算法:
对于列限制不合法的方案数。
首先枚举不合法的列数 \(c\)。
dp 的设计很巧妙,由于我们只关心第 \(c\) 列,其他列之间的区别不重要,所以将其他列融合成一个状态。设 \(f[i,j,k]\) 为前 \(i\) 行,第 \(c\) 列选了 \(j\) 个,其他列选了 \(k\) 个的方案数。
分成第 \(i\) 行不选,选第 \(c\) 列,选其他列来转移。
选第 \(c\) 列的方案有 \(a_{i,c}\) 种,则选其他列的方案有 \(\sum\limits_{j=1}^m a_{i,j}-a_{i,c}\) 种,所以我们需要用一个 \(s\) 数组预处理第 \(i\) 行所有 \(a\) 之和。
\(f[i,j,k]=f[i-1,j,k]+a_{i,c}*f[i-1,j-1,k]+(s_i-a_{i,c})*f[i-1,j,k-1]\)
最后的答案要满足第 \(c\) 列不合法且至少选一个,所以是 \(\sum\limits_{j=1}^n\sum\limits_{k=0}^{\min(j-1,n-j)}f[n,j,k]\)。(\(j-1\) 保证 \(j>k\) ,\(n-j\) 保证 \(j+k\leq n\))
初值是 \(f[0,0,0]=1\),状态数是 \(n^3\) 枚举了 \(m\) ,所以时间复杂度是 \(O(n^3m)\)。
对于不计列限制的方案数。
只限制每行最多选一个,我们可以运用组合数学,每行的方案数是 \(s_i+1\) (选或不选),所以只用把所有 \(s_i+1\) 乘起来,又因为 \(k\geq1\) ,所以最后减掉一个完全没有选的方案即可。即 \(\prod\limits_{i=1}^n(s_i+1)-1\)。
时间复杂度 \(O(n)\)。
优化:
上面的算法时间复杂度 \(O(n^3m)\),可以获得84分的好成绩。
于是对于列限制不合法的 dp 又有了一个更神仙的优化:我们发现要判断不合法方案不需要 \(j\) 和 \(k\) 的具体数值而只需要他们的相对大小,所以将“第 \(c\) 列选了 \(j\) 个,其他列选了 \(k\) 个”的状态再融合成"第 \(c\) 列比其他列多选 \(j\) 个",那么状态转移方程变为\(f[i,j]=f[i-1,j]+a_{i,c}*f[i-1,j-1]+(s_i-a_{i,c})*f[i-1,j+1]\)。
由于 \(j\) 会有负数的情况,所以将 dp 第二维参与操作时全部加上 \(n\)。
初值是 \(f[0,0+n]=1\),时间复杂度降为 \(O(n^2m)\),可以通过此题。
代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define in read()
inline int read(){
int p=0,f=1;
char c=getchar();
while(c>'9'||c<'0'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){p=p*10+c-'0';c=getchar();}
return p*f;
}
const int mod=998244353;
int n,m;
int a[105][2005];
int s[105];
int f[105][205];
int ans1,ans2;
inline int add(int a,int b){return (a+b)%mod;}
inline int mul(int a,int b){return a*b%mod;}
signed main(){
n=in,m=in;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
a[i][j]=in;
s[i]=add(s[i],a[i][j]);
}
for(int c=1;c<=m;c++){
memset(f,0,sizeof(f));
f[0][n+0]=1;
for(int i=1;i<=n;i++)
for(int j=-i;j<=i;j++){
f[i][n+j]=f[i-1][n+j];
if(j>-i)f[i][n+j]=add(f[i][n+j],mul(f[i-1][n+j-1],a[i][c]));
if(j<i)f[i][n+j]=add(f[i][n+j],mul(f[i-1][n+j+1],((s[i]-a[i][c])%mod+mod)%mod));
}
for(int j=1;j<=n;j++)
ans1=add(ans1,f[n][n+j]);
}
ans2=1;
for(int i=1;i<=n;i++)
ans2=mul(ans2,s[i]+1);
ans2-=1;
cout<<((ans2-ans1)%mod+mod)%mod;
return 0;
}
题外话:
这个 dp 加容斥好难啊我趣
洛谷 P5664 [CSP-S2019] Emiya 家今天的饭的更多相关文章
- 洛谷P5664 Emiya 家今天的饭 问题分析
首先来看一道我编的题: 安娜写宋词 题目背景 洛谷P5664 Emiya 家今天的饭[民间数据] 的简化版本. 题目描述 安娜准备去参加宋词大赛,她一共掌握 \(n\) 个 词牌名 ,并且她的宋词总共 ...
- 洛谷P5664 Emiya 家今天的饭 题解 动态规划
首先来看一道题题: 安娜写宋词 题目背景 洛谷P5664 Emiya 家今天的饭[民间数据] 的简化版本. 题目描述 安娜准备去参加宋词大赛,她一共掌握 \(n\) 个 词牌名 ,并且她的宋词总共有 ...
- 【CSP-S 2019】【洛谷P5664】Emiya 家今天的饭【dp】
题目 题目链接:https://www.luogu.org/problem/P5664 Emiya 是个擅长做菜的高中生,他共掌握 \(n\) 种烹饪方法,且会使用 \(m\) 种主要食材做菜.为了方 ...
- 洛谷 P5664 Emiya 家今天的饭(84分)
题目传送门 解题思路: 对于每一个列c,f[i][j][k]表示到第i行,第c列选了j个,其它列一共选了k个,然后我们读题意发现只要j>k,那就一定是不合法的,然后统计所有方案,减去所有不合法方 ...
- 洛谷 P3258 松鼠的新家 题解
题面 貌似这道题暴力加玄学优化就可以AC? 下面是正解: 1.树链剖分: 我们在u到v之间都放一个糖果,可以将松鼠它家u到v的糖果数都加1.每一次将a[i]到a[i+1] (a数组是访问顺序)的节点加 ...
- 洛谷 [P3258] 松鼠的新家
树上差分 对于一条路径 \(u->v\) 来说,设 \(t=LCA(u,v)\) ,d[]为差分数组 ,则有 d[u]++;d[v]++;d[t]--;d[fa[t]]--; 注意:题目中所给的 ...
- 洛谷P3258 松鼠的新家
树上差分 这应该是一道很简单的树上差分了..就是问每个点被覆盖了多少次. 要注意我们最后dfs后,要把除第一个节点以外的所有点的-1,因为有些点作为起点和终点覆盖了两次,按照题目意思是不用覆盖两次的. ...
- 洛谷P3258松鼠的新家
题目传送门 恩,很明显的一个树剖题,配合树上差分其实也并不难,不过无奈蒟蒻树剖还没那么熟练,而且树上差分也做的少,所以这题愣是做了一中午......唉,果然我还是太菜了.恩,具体做法在代码中解释吧: ...
- Emiya家今天的饭 NOIP2019 (CSP?) 类DP好题 luoguP5664
luogu题目传送门! 首先,硬求可行方案数并不现实,因为不好求(去年考场就这么挂的,虽然那时候比现在更蒟). 在硬搞可行方案数不行之后,对题目要求的目标进行转换: 可行方案数 = 总方案数 - 不合 ...
随机推荐
- 借助AWR报告分析解决oracleCPU过高的问题
原文地址:http://www.cnblogs.com/crystal-guoguo/p/4213458.html 简介:在oracle数据库中,有两个非常实用的自带监控工具EM(Enterprise ...
- FastAPI(4)- get 请求 - 路径参数 Path Parameters
什么是路径 假设一个 url 是: http://127.0.0.1:8080/items/abcd 那么路径 path 就是 /items/abcd 路径参数 就是将路径上的某一部分变成参数,可通过 ...
- layui日期选择无效的问题
解决layui引入时间控件无效的问题 - 简书 (jianshu.com) 原因是因为在使用日期选择器的时候,layui源码里有一个laydate.css文件找不到 将下载的文档文件里的css文件夹, ...
- 【PHP数据结构】顺序表(数组)的相关逻辑操作
在定义好了物理结构,也就是存储结构之后,我们就需要对这个存储结构进行一系列的逻辑操作.在这里,我们就从顺序表入手,因为这个结构非常简单,就是我们最常用的数组.那么针对数组,我们通常都会有哪些操作呢? ...
- Shell系列(21)- 字符截取命令printf
作用 printf是标准格式输出命令,控制输出格式,不会自动加入换行符.awk会用到该条命令 命令 printf '输出类型输出格式' 输出内容 #''双引号不能少,输出类型和输出格式之间没有空格 输 ...
- jmeter简单的压测案例——访问百度并发5,持续请求15
好记性不如烂笔头,还是记记吧. jmeter压测的指标有很多,先从简单的入手,下一章节再讲解jmeter组件的作用. 需求是:访问百度的压测指标是:10s内并发5,持续15次请求.那么需要设置以下几点 ...
- P7581-「RdOI R2」路径权值【长链剖分,dp】
正题 题目链接:https://www.luogu.com.cn/problem/P7581 题目大意 给出\(n\)个点的有边权有根树,\(m\)次询问一个节点\(x\)的所有\(k\)级儿子两两之 ...
- Python接口自动化测试概念以及意义
接口定义: 接口普遍有两种意思,一种是API(Application Program Interface),应用编程接口,它是一组定义.程序及协议的集合,通过API接口实现计算机软件之间的相互通信.而 ...
- NOIP模拟66
T1 接力比赛 解题思路 其实就是一个背包 DP ,也没啥好说的也就是一个优化,每次枚举之前的前缀和. 比较妙的就是一个 random_shuffle 可以整掉部分卡人的数据(但是好像 sort 一下 ...
- JS中call,apply,bind的区别
1.关于this对象的指向,请看如下代码 var name = 'jack'; var age = 18; var obj = { name:'mary', objAge:this.age, myFu ...