【noip2019提高组】Emiya 家今天的饭
题面
思路
一道很妙的dp题,首先可以发现至多有一种主要食材被选超过t/2次,所以考虑用容斥:合法方案=总方案数-Σ每行的不合法方案数。显然:求出确定的某一列不合法的方案数比求出每一行都合法的方案数要简单得多(范围减小,限制更明确)。
..................这也是容斥的基本思路:正难则反....................
根据这个可以列出朴素的dp转移方程:令f[i][j][k]表示前i行在当前第p列中选了j个,其它列中选了k个;s[i]表示第i行总共有多少道菜。
f[i][j][k]=f[i-1][j][k]+a[i][p]*f[i-1][j-1][k]+(s[i]-a[i][k])*f[i-1][j][k-1]
但是方程转移是n^3的,加上枚举每一列,总时间复杂度是O(m*n^3)的,很明显过不了这题。接下来考虑优化:
再仔细读题,不合法的方案是选当前列次数超过了选其他列的个数,也就是说,我们只需要记录j和k的相对数量,于是可以将这两维压成一维。令f[i][j]表示前i列选当前行比其他行多j次的方案数。转移方程基本同上。
剩下的问题就是如何求总方案:本人比较蒟蒻地又用了一个dp。g[i][j]表示前i行选了j次的方案数。g[i][j]=g[i-1][j]+g[i-1][j-1]*s[i];总方案即Σg[n][i]。但其实有更简单的方法:总方案=Π(s[i]+1)-1;即每行选任意一道菜,也可不选。但要减去总共选0道菜的方案。
代码
几个细节:
*枚举每列时记得清空f数组
*注意ans-f[n][i]时因为取了模可能减为负数
*f[i][j]的j这一维可能为负数,为了好处理,将j变为j+n;转移时只需从n-i枚举到n+i,容斥时只需从n+1枚举到n+n即可
1 #include<bits/stdc++.h> 2 #define ll long long 3 using namespace std; 4 const int inf=998244353; 5 long long f[105][305],g[105][105],s[105],ans; 6 int a[105][2005]; 7 int main(){ 8 int i,j,n,m,k,l; 9 scanf("%d%d",&n,&m); 10 for(i=1;i<=n;i++){ 11 for(j=1;j<=m;j++){ 12 scanf("%d",&a[i][j]); 13 s[i]=(s[i]+a[i][j])%inf; 14 } 15 } 16 g[0][0]=1; 17 for(i=1;i<=n;i++){ 18 g[i][0]=1; 19 for(j=1;j<=n;j++) 20 g[i][j]=(g[i-1][j]+g[i-1][j-1]*s[i]%inf)%inf; 21 } 22 for(i=1;i<=n;i++) 23 ans=(ans+g[n][i])%inf; 24 for(k=1;k<=m;k++){ 25 memset(f,0,sizeof(f)); 26 f[0][n]=1; 27 for(i=1;i<=n;i++){ 28 for(j=n-i;j<=n+i;j++){ 29 f[i][j]=(f[i][j]+f[i-1][j]+f[i-1][j-1]*a[i][k]%inf+f[i-1][j+1]*(s[i]-a[i][k])%inf)%inf; 30 } 31 } 32 for(i=n+1;i<=n+n;i++) 33 ans=(ans-f[n][i]+inf)%inf; 34 } 35 printf("%lld",ans); 36 return 0; 37 }