题解 P5664 【Emiya 家今天的饭【民间数据】】
周子衡
·
·
题解
又是趁着还没有题解,蒟蒻我来讲讲思路……
可以发现,从正面计算每种食材不超过一半的方案数比较困难,于是我们从反面考虑,总的方案数显然是(\prod_{i=1}^n(1+\sum_{j=1}^ma_{i,j}))-1(每种烹饪方式要么有一道菜要么一道也没有,减去什么都不选的一种),而要排除的方案数就是有至少一种食材超过一半的的方案数。
而又因为至多有一种食材超过一半,所以可以转化为:依次求出m种食材各自超过一半的方案数,然后一起减去。
很容易想到\text{DP}。记dp_{i,j,k,l}为在前i种烹饪方式中,选出k道菜,使得第j种食材恰有l次的方案数。易知
dp_{i,j,k,l}=dp_{i-1,j,k,l}+a_{i,j}dp_{i-1,j,k-1,l-1}+\sum_{1\leq j'\leq m,j'\neq j}a_{i,j'}dp_{i-1,j,k-1,l}
其中上式的三项分别为第i种烹饪方式什么也不选、选第j种食材、选除了第j种食材的其他食材,大家可以自行验证。记rs_i=\sum_{j=1}^ma_{i,j},于是\sum_{1\leq j'\leq m,j'\neq j}a_{i,j'}=rs_i-a_{i,j},上式转移可以在O(1)内实现,状态总数为O(n^3m),故总时间复杂度为O(n^3m)。最后枚举j,k,l(2l>k)即可。得分84分。(出考场听到有84分我都震惊了)
考虑优化。鉴于转移是O(1)的,考虑优化状态。可以发现,如果要使第j种食材过半,等价于使第j种食材数量大于其他所有食材数量之和,而与上文的k,l无关。于是改进状态设计:
记dp_{i,j,k}表示在前i种烹饪方式中,使得第j种食材的数量减去其他食材的数量和的结果恰为k 的方案数(k可为负数,k\in [-n,n])。可写出新的方程
dp_{i,j,k}=dp_{i-1,j,k}+a_{i,j}dp_{i-1,j,k-1}+\sum_{1\leq j'\leq m,j'\neq j}a_{i,j'}dp_{i-1,j,k+1}
其中上式的三项同样分别为第i种烹饪方式什么也不选、选第j种食材、选除了第j种食材的其他食材。上式转移同样可以在O(1)内实现,状态总数为O(n^2m),故总时间复杂度为O(n^2m)。最后枚举所有j,k(k>0)即可。可以拿到满分。(貌似和上一个做法得分相差不大,可怜我白写了……)
代码蒟蒻我还没拿到,也懒得再打(其实是怕打错然后发慌),就不贴了。
```cpp
#include<cstdio>
#include<cstring>
using namespace std;
const long long MOD=998244353;
long long a[200][3000],rs[200];
int n=0,m=0;
inline int hsh(int x){return x+n+10;}
long long dp[200][500];
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
scanf("%lld",&a[i][j]);
rs[i]=(rs[i]+a[i][j])%MOD;
}
}
long long sum=1;for(int i=1;i<=n;i++)sum=(sum*(rs[i]+1))%MOD;sum=(sum+MOD-1)%MOD;
for(int j=1;j<=m;j++)
{
memset(dp,0,sizeof(dp));
dp[1][hsh(0)]=1,dp[1][hsh(1)]=a[1][j],dp[1][hsh(-1)]=(rs[1]+MOD-a[1][j])%MOD;
for(int i=2;i<=n;i++)
{
for(int k=-n;k<=n;k++)
{
dp[i][hsh(k)]=(dp[i-1][hsh(k)]+a[i][j]*dp[i-1][hsh(k-1)]%MOD+(rs[i]+MOD-a[i][j])*dp[i-1][hsh(k+1)]%MOD)%MOD;
}
}
for(int k=1;k<=n;k++)sum=(sum+MOD-dp[n][hsh(k)])%MOD;
}
printf("%lld",sum);
return 0;
}
```