重做下去年的CSP题找找感觉,去年D1T2写可持久化线段树上二分的悲惨经历让我对D1T2充满了厌恶(好吧其实是正解在简单都懒得写了),因此就来改这个去年没调出来的DP了
首先这个主要食材占一半一眼容斥,因此我们大体思路就有了
先求出不管这个限制的总方案数,设(f_{i,j})表示前(i)种方法中做了(j)道菜的方案数,记录一个每行的和显然可以(O(n^2))转移
考虑大力枚举哪个食材(p)超过了(lfloor frac{k}{2} floor),显然可以再做一次DP,设(g_{i,j,k})表示前(i)种方法中做了(j)道菜的方案数,其中有(k)道菜使用了食材(p),转移显然
但这样总复杂度是(O(n^3m))无法通过,放一下代码意思一下
#include<cstdio>
#include<cstring>
#define RI register int
#define CI const int&
using namespace std;
const int N=105,M=2005,mod=998244353;
int n,m,a[N][M],f[N][N],g[N][N][N],sum[N],ans;
int main()
{
RI i,j,k,p; for (scanf("%d%d",&n,&m),i=1;i<=n;++i)
for (j=1;j<=m;++j) scanf("%d",&a[i][j]),(sum[i]+=a[i][j])%=mod;
for (f[0][0]=1,i=1;i<=n;++i) for (j=0;j<=i;++j)
f[i][j]=(f[i-1][j]+(j?1LL*f[i-1][j-1]*sum[i]%mod:0))%mod;
for (i=1;i<=n;++i) (ans+=f[n][i])%=mod;
for (p=1;p<=m;++p)
{
for (g[0][0][0]=i=1;i<=n;++i) for (j=0;j<=i;++j) for (k=0;k<=j;++k)
g[i][j][k]=(1LL*g[i-1][j][k]+(j?1LL*g[i-1][j-1][k]:0)*(sum[i]-a[i][p]+mod)%mod+
(j&&k?1LL*g[i-1][j-1][k-1]*a[i][p]%mod:0))%mod;
for (i=1;i<=n;++i) for (j=(i>>1)+1;j<=n;++j) (ans+=mod-g[n][i][j])%=mod;
}
return printf("%d",ans),0;
}
我们仔细观察一下转移方程发现我们只需要知道后两维的差即可(话说这一年做的AGC中有太多这样的套路了),因此直接压成一维总复杂度就是(O(n^2m))的了
#include<cstdio>
#include<cstring>
#define RI register int
#define CI const int&
using namespace std;
const int N=105,M=2005,mod=998244353;
int n,m,a[N][M],f[N][N],g[N][N<<1],sum[N],ans;
int main()
{
RI i,j,p; for (scanf("%d%d",&n,&m),i=1;i<=n;++i)
for (j=1;j<=m;++j) scanf("%d",&a[i][j]),(sum[i]+=a[i][j])%=mod;
for (f[0][0]=1,i=1;i<=n;++i) for (j=0;j<=i;++j)
f[i][j]=(f[i-1][j]+(j?1LL*f[i-1][j-1]*sum[i]%mod:0))%mod;
for (i=1;i<=n;++i) (ans+=f[n][i])%=mod;
for (p=1;p<=m;++p)
{
for (g[0][n]=i=1;i<=n;++i) for (j=-i;j<=i;++j)
g[i][n+j]=(1LL*g[i-1][n+j]+1LL*g[i-1][n+j-1]*a[i][p]%mod+
1LL*g[i-1][n+j+1]*(sum[i]-a[i][p]+mod)%mod)%mod;
for (i=1;i<=n;++i) (ans+=mod-g[n][n+i])%=mod;
}
return printf("%d",ans),0;
}