洛谷 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题目传送门! 首先,硬求可行方案数并不现实,因为不好求(去年考场就这么挂的,虽然那时候比现在更蒟). 在硬搞可行方案数不行之后,对题目要求的目标进行转换: 可行方案数 = 总方案数 - 不合 ...
随机推荐
- shell脚本———双重循环——九九乘法表
1.基础双重循环模板 2.break跳出单个循环 3.continue中止某次循环中的命令,但不会完全中止整个命令 4.九九乘法表
- MongoDB索引的简单理解
目录 MongoDB索引 1.语法准备 2.数据准备: 3.索引 3.1 唯一索引 3.2 单键索引 3.3 多键索引 3.4 复合索引 3.5 交叉索引 3.6 部分索引 3.7覆盖索引 3.8 全 ...
- 链表 Linked List
目录 链表介绍 单链表 单链表的应用实例 添加-直接添加到末尾 添加-顺序添加 更新 删除 单链表的面试题 双链表 链表介绍 链表时有序的列表,但是它在内存中是存储如下 小结 链表是以节点的方式来存储 ...
- java 笔记一些
面象对的umv 视图示例: 用户 银行 帐户 怎么关联 帐户代码: 文件命名 Account package com.atuigu.exer4; import org.omg.CORBA.PUB ...
- Spring Boot 2.x 之构建Fat Jar和可执行Jar
Spring Boot提供的Maven插件spring-boot-maven-plugin可以用来构建Fat Jar和可执行Jar. 1.Fat Jar Fat Jar需要使用 java -jar x ...
- 源码解析Grpc拦截器(C#版本)
前言 其实Grpc拦截器是我以前研究过,但是我看网上相关C#版本的源码解析相对少一点,所以笔者借这篇文章给大家分享下Grpc拦截器的实现,废话不多说,直接开讲(Grpc的源码看着很方便,包自动都能还原 ...
- 255 day03_List、Set、数据结构、Collections
day03 [List.Set.数据结构.Collections] 主要内容 数据结构 List集合 Set集合 Collections 教学目标 [ ] 能够说出List集合特点 [ ] 能够说出常 ...
- Java数学函数的使用
Java的Math类中提供了一系列关于数学运算的静态方法,常见的运算整理如下[1] 算数运算 Math.sqrt() // 平方根 Math.cbrt() // 立方根 Math.pow(a, b) ...
- PHP中的日期相关函数(三)
之前我们已经介绍过了 PHP 的一些相关的日期操作对象,今天我们就来学习剩下的那些面向过程的使用方式.当然,如果是和 DateTime 类中相似的方法我们就不再进行介绍了.另外,Date() 和 ti ...
- [转载]解决虚拟机中Centos7出现错误:Failed to start LSB: Bring up/down networking
1.执行 service network restart 出现以下错误 Restarting network (via systemctl): Job for network.service fai ...