链接:

P5664


题意:

给出一个 \(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 家今天的饭的更多相关文章

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

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

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

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

  3. 【CSP-S 2019】【洛谷P5664】Emiya 家今天的饭【dp】

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

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

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

  5. 洛谷 P3258 松鼠的新家 题解

    题面 貌似这道题暴力加玄学优化就可以AC? 下面是正解: 1.树链剖分: 我们在u到v之间都放一个糖果,可以将松鼠它家u到v的糖果数都加1.每一次将a[i]到a[i+1] (a数组是访问顺序)的节点加 ...

  6. 洛谷 [P3258] 松鼠的新家

    树上差分 对于一条路径 \(u->v\) 来说,设 \(t=LCA(u,v)\) ,d[]为差分数组 ,则有 d[u]++;d[v]++;d[t]--;d[fa[t]]--; 注意:题目中所给的 ...

  7. 洛谷P3258 松鼠的新家

    树上差分 这应该是一道很简单的树上差分了..就是问每个点被覆盖了多少次. 要注意我们最后dfs后,要把除第一个节点以外的所有点的-1,因为有些点作为起点和终点覆盖了两次,按照题目意思是不用覆盖两次的. ...

  8. 洛谷P3258松鼠的新家

    题目传送门 恩,很明显的一个树剖题,配合树上差分其实也并不难,不过无奈蒟蒻树剖还没那么熟练,而且树上差分也做的少,所以这题愣是做了一中午......唉,果然我还是太菜了.恩,具体做法在代码中解释吧: ...

  9. Emiya家今天的饭 NOIP2019 (CSP?) 类DP好题 luoguP5664

    luogu题目传送门! 首先,硬求可行方案数并不现实,因为不好求(去年考场就这么挂的,虽然那时候比现在更蒟). 在硬搞可行方案数不行之后,对题目要求的目标进行转换: 可行方案数 = 总方案数 - 不合 ...

随机推荐

  1. 法术迸发(Spellburst)

    描述 法术迸发 (EN:Spellburst ) 是一种在<通灵学园>中加入的关键字异能,在玩家打出一张法术牌后触发,只能触发一次. 若随从在法术结算过程中死亡,则不会触发效果 思路 首先 ...

  2. Nginx总结(九)Nginx服务器高性能优化的配置--轻松实现10万并发访问量

    前面讲了如何配置Nginx虚拟主机,如何配置服务日志等很多基础的内容,大家可以去这里看看nginx系列文章:https://www.cnblogs.com/zhangweizhong/category ...

  3. 一文搞懂如何使用Node.js进行TCP网络通信

    摘要: 网络是通信互联的基础,Node.js提供了net.http.dgram等模块,分别用来实现TCP.HTTP.UDP的通信,本文主要对使用Node.js的TCP通信部份进行实践记录. 本文分享自 ...

  4. 边缘使用 K8s 门槛太高?OpenYurt 这个功能帮你快速搭建集群!

    OpenYurt作为阿里巴巴首个开源的边缘云原生项目,涉及到边缘计算和云原生两个领域.然而,许多边缘计算的开发者并不熟悉云原生相关的知识.为了降低 OpenYurt 的使用门槛,帮助更多地开发者快速上 ...

  5. 改Jupyter Notebook的默认工作路径?

    如何更改Jupyter Notebook的默认工作路径? 1.在cmd中输入命令使Jupyter产生配置文件:Jupyter_notebook_config.py jupyter notebook - ...

  6. PHP的另一个高效缓存扩展:Yac

    之前的文章中我们已经学习过一个 PHP 自带的扩展缓存 Apc ,今天我们来学习另一个缓存扩展:Yac . 什么是 Yac 从名字其实就能看出,这又是鸟哥大神的作品.毕竟是 PHP 的核心开发人员,他 ...

  7. webpack learn1-配置项目加载各种静态资源及css预处理器2

    继续在webpack.config.js中配置loader { test:/\.css$/, use: [ 'style-loader', 'css-loader' ] },{ test:/\.(jp ...

  8. 小学生都能读懂的网络协议之:WebSocket

    目录 简介 webSocket vs HTTP HTTP upgrade header websocket的优点 webScoket的应用 websocket的握手流程 WebSocket API 总 ...

  9. java中的swing设计界面时怎么加上背景图片。而不覆盖其他控件?

    通过以下方式设置下背景就可以了: import java.awt.Container; import javax.swing.ImageIcon; import javax.swing.JFrame; ...

  10. 从零开始部署 Yapi(Windows+Nginx)

    一.环境准备及安装 本文中是以本地 Windows 作为安装环境,Nginx 做反向代理,亲测验证可用. Yapi 运行需要的环境: Nodejs,MongoDB 安装包都在文档末尾处 1.1 安装 ...