\(\text{Solution:}\)
又是一個經典題目……一直都不太會 肝了兩天算是搞明白了
首先,觀察到題目限制應該不難想到一個容斥。因爲思考一下發現這兩個限制同時滿足難以表達在狀態裏,因爲我們不能對每一道食材都記錄它的出現次數。同時還有 至少 這個詞彙。那麼對誰容斥?
那應該就是對 不超過一半 的這個限制容斥了。現在讓我們枚舉一道食材,讓它不滿足這個限制,也就是說 強制出現次數大於 \(\frac{n}{2}\) 次。
那我們又可以發現,剩下的食材無論怎麼選擇都不會再出現第二個不合法的了!
那麼最終的方案數得以輕鬆表示:用總方案數減掉每一道菜不合法的方案數。
現在的問題就轉化爲一個求不合法方案數的問題。
那麼,設 \(f[i][j][k]\) 表示前 \(i\) 種方法,選擇了 \(j\) 種,其中有 \(k\) 種是不合法食材做的菜的搭配方案數。
那麼就會有一個簡單的 \(dp:\)
其中,\(sum[i]\) 表示這一種方法所能做的菜的總數, \(a[i][pos]\) 表示用這種方法與 \(pos\) 食材所能做的菜的種類數。
於是上面的方程就分別對應了:不選,選擇一個不合法食材,選擇一個合法食材的三種轉移。
初始值:\(f[i][0][0]=1,\) 轉移的時候不要忘記轉移 \(f[i][j][0]!!\)
#include<bits/stdc++.h>
using namespace std;
const int N=101;
int f[N][N][N],a[N][2001];
int sum[N],n,m;
const int mod=998244353;
inline int Add(int x,int y){return (x+y)%mod;}
inline int Mul(int x,int y){return 1ll*x*y%mod;}
inline int Max(int x,int y){return x>y?x:y;}
inline int Min(int x,int y){return x<y?x:y;}
inline int Dec(int x,int y){return (x-y+mod)%mod;}
int main(){
freopen("in.txt","r",stdin);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i){
for(int j=1;j<=m;++j){
scanf("%d",&a[i][j]);
}
}
for(int i=1;i<=n;++i){
for(int j=1;j<=m;++j){
sum[i]=Add(sum[i],a[i][j]);
}
}
int All=1;
for(int i=1;i<=n;++i)All=Mul(All,sum[i]+1);
for(int pos=1;pos<=m;++pos){
memset(f,0,sizeof f);
f[0][0][0]=1;
for(int i=1;i<=n;++i){
f[i][0][0]=1;
for(int j=1;j<=n;++j){
f[i][j][0]=Add(f[i-1][j][0],Mul(f[i-1][j-1][0],sum[i]-a[i][pos]));////f[i][j][0]!
for(int k=1;k<=n;++k){
f[i][j][k]=Add(f[i][j][k],f[i-1][j][k]);
f[i][j][k]=Add(f[i][j][k],Mul(f[i-1][j-1][k-1],a[i][pos]));
f[i][j][k]=Add(f[i][j][k],Mul(f[i-1][j-1][k],sum[i]-a[i][pos]));
}
}
}
for(int i=0;i<=n;++i){
for(int j=(i/2)+1;j<=n;++j)
All=Dec(All,f[n][i][j]);
}
}
printf("%d\n",All-1);
return 0;
}
這樣就有了 \(84pts\)
繼續考慮,把 \(O(n^3m)\) 優化到 \(O(n^2m)\) 就完事了。
觀察一下,我們能不能把 選擇多少道菜 和 選了多少道不合法的菜 合併到一起?
仔細想想,如果我們知道在一種選擇方案下,選擇的 \(pos\) 菜品的數量比其他的更多,那麼我們實際上無需知道一共選擇了多少個,只需要知道這種情況的方案數然後減掉即可。
那麼我們就可以考慮換一個狀態:設 \(f[i][j]\) 表示前 \(i\) 種食材,選擇了 \(pos\) 食材的食品數量與其他的食材食品數量之差爲 \(j\) 的方案數。
注意這裏的 \(j\) 可以是負數,所以要整體平移。
那麼就可以同樣地寫出方程:
初始值也是一樣的,而這裏注意一下減的時候要從 \(pos\) 數目嚴格大於的時候開始算。
如果 \(j=0\) 實際是合法方案數。
這樣少掉一層循環,複雜度就是 \(O(n^2m)\) 了。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=501;
int f[N][N],a[N][5001];
int sum[N],n,m;
const int mod=998244353;
inline int Add(int x,int y){return (x+y)%mod;}
inline int Mul(int x,int y){return 1ll*x*y%mod;}
inline int Max(int x,int y){return x>y?x:y;}
inline int Min(int x,int y){return x<y?x:y;}
inline int Dec(int x,int y){return (x-y+mod)%mod;}
inline int getpos(int x){return (x+n+1);}
void print(int P){
printf("%d:\n",P);
for(int i=0;i<=n;++i)
for(int j=-n;j<=n;++j)
printf("%d%c",f[i][getpos(j)],j==n?'\n':' ');
puts("");
}
signed main(){
freopen("meal.in","r",stdin);
freopen("meal.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i){
for(int j=1;j<=m;++j){
scanf("%d",&a[i][j]);
}
}
for(int i=1;i<=n;++i){
for(int j=1;j<=m;++j){
sum[i]=Add(sum[i],a[i][j]);
}
}
int All=1;
for(int i=1;i<=n;++i)All=Mul(All,sum[i]+1);
for(int pos=1;pos<=m;++pos){
memset(f,0,sizeof f);
f[0][n+1]=1;
for(int i=1;i<=n;++i){
for(int j=-n;j<=n;++j){
int poss=getpos(j);
f[i][poss]=Add(f[i-1][poss],f[i][poss]);
f[i][poss]=Add(f[i][poss],Mul(f[i-1][poss-1],a[i][pos]));
f[i][poss]=Add(f[i][poss],Mul(f[i-1][poss+1],(sum[i]-a[i][pos])));
}
}
for(int i=1;i<=n;++i)All=Dec(All,f[n][getpos(i)]);
}
printf("%lld\n",All-1);
return 0;
}