P5664 [CSP-S2019] Emiya 家今天的饭
题目描述
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
输入 #1
2 3
1 0 1
0 1 1
输出 #1
3
输入输出样例 #2
输入 #2
3 3
1 2 3
4 5 0
6 0 0
输出 #2
190
输入输出样例 #3
输入 #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, j\),Emiya 都最多只会做一道菜,因此我们直接通过给出烹饪方法、主要食材的编号来描述一道菜。
符合要求的方案包括:
- 做一道用烹饪方法 1、主要食材 1 的菜和一道用烹饪方法 2、主要食材 2 的菜
- 做一道用烹饪方法 1、主要食材 1 的菜和一道用烹饪方法 2、主要食材 3 的菜
- 做一道用烹饪方法 1、主要食材 3 的菜和一道用烹饪方法 2、主要食材 2 的菜
因此输出结果为 \(3 \bmod 998,244,353 = 3\)。 需要注意的是,所有只包含一道菜的方案都是不符合要求的,因为唯一的主要食材在超过一半的菜中出现,这不满足 Yazid 的要求。
【样例 2 解释】
Emiya 必须至少做 \(2\) 道菜。
做 \(2\) 道菜的符合要求的方案数为 \(100\)。
做 \(3\) 道菜的符合要求的方案数为 \(90\)。
因此符合要求的方案数为 \(100 + 90 = 190\)。
【数据范围】
::cute-table{tuack}
测试点编号 | \(n=\) | \(m=\) | \(a_{i,j}<\) |
---|---|---|---|
\(1\) | \(2\) | \(2\) | \(2\) |
\(2\) | ^ | \(3\) | ^ |
\(3\) | \(5\) | \(2\) | ^ |
\(4\) | ^ | \(3\) | ^ |
\(5\) | \(10\) | \(2\) | ^ |
\(6\) | ^ | \(3\) | ^ |
\(7\) | \(10\) | \(2\) | \(1000\) |
\(8\) | ^ | \(3\) | ^ |
\(9\sim 12\) | \(40\) | \(2\) | ^ |
\(13\sim16\) | ^ | \(3\) | ^ |
\(17\sim21\) | ^ | \(500\) | ^ |
\(22\sim25\) | \(100\) | \(2000\) | \(998,244,353\) |
对于所有测试点,保证 \(1 \leq n \leq 100\),\(1 \leq m \leq 2000\),\(0 \leq a_{i,j} \lt 998,244,353\)。
解题报告
参考题解:题解 P5664
好的,我还是一碰 DP 就死呢(无言了)。
考虑对题目中的限制作进一步的表述。
其中,对于列的性质,发现若有不合法的列,则必然有且仅有一列不合法,这是因为不可能有不同的两列的数量都大于总数的一半。
发现列的限制很容易容斥:每行选不超过一个的方案数 \(-\) 每行选不超过一个,且某一列选了超过一半的方案数。
那么直接枚举不合法的列 \(t\),我们就只关心一个数的位置是否在当前列;如果属于在其他列的情况,那么它具体在哪一列对当前列的合法性并无影响,我们并不需要考虑。
设计状态:\(dp_{i,j,k}\) 表示对于非法列 \(t\),前 \(i\) 行选了 \(j\) 个在该列,选了 \(k\) 个在其他列,另设 \(s_i\) 表示第 \(i\) 的总和,那么有:
非法的方案数就是累加每一个列 \(t\) 的 \(\sum_{j>k} dp_{n,j,k}\)。
接下来考虑计算总方案数:和之前相似,只需设 \(g_{i,j}\) 为前 \(i\) 行共选了 \(j\) 个数的方案数,则有转移:
那么 \(\sum_{i=1}^{n} g_{n,i}\) 就是总方案数, 这一步是 \(O(n^2)\) 的。所以现在可以在 \(O(mn^3)\) 的总复杂度内完成这题。
进一步优化,注意到在不合法情况的计算过程中,我们实际上并不关心 \(j\)、\(k\) 的具体数值,而只关心相对的大小关系。
那么我们可以重新设 \(dp_{i,j}\) 表示表示前 \(i\) 行,当前列的数比其他列的数多了 \(j\) 个,则有:
复杂度 \(O(mn^2)\),代码如下:
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int INF=0x3f3f3f3f;
const int mod=998244353;
const int N=110;
const int M=4110;#define ckmax(x,y) ( x=max(x,y) )
#define ckmin(x,y) ( x=min(x,y) )
#define Add(x,y) ( x=(x+y)%mod )inline int read()
{int f=1,x=0; char ch=getchar();while(!isdigit(ch)) { if(ch=='-') f=-1; ch=getchar(); }while(isdigit(ch)) { x=x*10+ch-'0'; ch=getchar(); }return f*x;
}int n,m,ans;
int w[N][M],s[N][M];
int dp[N][M],g[N][M];signed main()
{// freopen("P5664.in","r",stdin);// freopen("P5664.out","w",stdout);n=read(),m=read();for(int i=1;i<=n;i++)for(int j=1;j<=m;j++){w[i][j]=read();Add(s[i][0],w[i][j]);}for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)s[i][j]=(s[i][0]-w[i][j]+mod)%mod;for(int k=1;k<=m;k++){memset(dp,0,sizeof(dp)); dp[0][n]=1;for(int i=1;i<=n;i++)for(int j=n-i;j<=n+i;j++)dp[i][j]=( dp[i-1][j]+dp[i-1][j-1]*w[i][k]%mod+dp[i-1][j+1]*s[i][k]%mod )%mod;for(int i=1;i<=n;i++) Add(ans,dp[n][i+n]);}g[0][0]=1;for(int i=1;i<=n;i++)for(int j=0;j<=n;j++)g[i][j]=( g[i-1][j]+(j>0 ? g[i-1][j-1]*s[i][0]%mod:0) )%mod;for(int i=1;i<=n;i++) ans=(ans-g[n][i]+mod)%mod;printf("%lld\n",ans*(mod-1)%mod);return 0;
}