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 总共会做 (sumlimits_{i=1}^{n} sumlimits_{j=1}^{m}a_{i,j})道不同的菜。
Emiya 今天要准备一桌饭招待 Yazid 和 Rin 这对好朋友,然而三个人对菜的搭配有不同的要求,更具体地,对于一种包含 (k) 道菜的搭配方案而言:
Emiya 不会让大家饿肚子,所以将做至少一道菜,即 (k geq 1)
Rin 希望品尝不同烹饪方法做出的菜,因此她要求每道菜的烹饪方法互不相同
Yazid 不希望品尝太多同一食材做出的菜,因此他要求每种主要食材至多在一半的菜(即 (lfloor frac{k}{2}
floor) 道菜)中被使用
这里的 (lfloor x
floor) 为下取整函数,表示不超过 (x) 的最大整数。
这些要求难不倒 Emiya,但他想知道共有多少种不同的符合要求的搭配方案。两种方案不同,当且仅当存在至少一道菜在一种方案中出现,而不在另一种方案中出现。
Emiya 找到了你,请你帮他计算,你只需要告诉他符合所有要求的搭配方案数对质数 (998,244,353998,244,353) 取模的结果。
数据范围
对于所有测试点,保证 (1 leq nleq100)
(1 leq m leq 2000,)
(0leq a_{i,j}lt 998,244,353。)
Solution
爆搜二三十分的样子
考虑容斥,先算出没有限制条件的所有情况,然后减去不合法的情况。
所有情况:
(dp1[i][j])表示前(i)种烹饪方法共选了(j)个食材。则有选和不选两种转移。
(dp1[i][j] = dp1[i - 1][j] + dp1[i - 1][j - 1] * sum[i])
非法情况:
对于每一个食品做一遍dp
考虑状态,因为食品个数小于(lfloor frac{j}{2}
floor)是合法,于是就想到把选了的第t个食品的数量和已经选了的烹饪方法的个数加入状态。
那么(dp2[i][j][k])表示前i行一共选了j种烹饪方法,第t个食品选了k个的方案数
于是非法的方案数就是(sumlimits_{k > lfloor frac{j}{2}
floor}{dp2[i][j][k]})
转移有三个:
(dp2[i][j][k] = dp2[i - 1][j][k] + dp2[i - 1][j - 1][k - 1] * a[i][j] + dp2[i - 1][j - 1][k] * (sum[i] - a[i][j]))
分别是不做菜,做菜选择第t个食品,做菜没选第t个食品
复杂度(O(mn^3)) 84分
考虑优化
其实非法的状况就是某一种食品数量大于其他种类食品的数量和。比如1,2,3三种食品,一共选了3道菜,食品1选了2种,食品2选了1种,2 > 1,该方案非法。
所以记录d =(非法食品数(k) - 合法食品数(j))代替上面的j,k两维,若d>0则非法。
(dp2[i][d] = dp2[i - 1][d] +dp2[i - 1][d - 1]* a[i][j] + dp2[i - 1][d + 1]* (sum[i] - a[i][j]))
(注意这个d可能小于0,为防止下标越界可以通通加上一个大数字)
然后就AC了
Code
#include<bits/stdc++.h>
using namespace std;
const long long N = 105,M = 2005,MOD = 998244353;
long long a[N][M],dp1[N][M],dp2[N][M],sum[N];
int n,m;
int main(){
cin >> n >> m;
for(int i = 1 ; i <= n; i++)
for(int j = 1; j <= m; j++){
scanf("%d",&a[i][j]);
sum[i] += a[i][j];
sum[i] %= MOD;
}
dp1[0][0] = 1;
for(int i = 1; i <= n; i++){
for(int j = 0; j <= i; j++)
dp1[i][j] = (dp1[i - 1][j] + dp1[i - 1][j - 1] * sum[i]) % MOD;
}
long long b = 0;
for(int k = 1; k <= m; k++){
memset(dp2,0,sizeof(dp2));
dp2[0][100] = 1;
for(int i = 1; i <= n; i++){
for(int d = i - 100; d <= i + 100; d++){
dp2[i][d + 100] = (dp2[i - 1][d + 100] + dp2[i - 1][d - 1 + 100] * a[i][k] + dp2[i - 1][d + 1 + 100] * (sum[i] - a[i][k])) % MOD;
}
}
for(int i = 101;i <= 100 + n; i++){
b += dp2[n][i];
b %= MOD;
}
}
long long ans = 0;
for(int i = 1; i <= M; i++)
ans = ans + dp1[n][i];
cout << (ans - b) % MOD << endl;
return 0;
}
借鉴了这位dalao的题解:https://blog.csdn.net/weixin_37517391/article/details/103110646