zoukankan      html  css  js  c++  java
  • 【题解】[CSP-S2019] Emiya 家今天的饭

    [CSP-S2019] Emiya 家今天的饭

    ( ext{Solution:})

    又是一个经典题目……一直都不太会 肝了两天算是搞明白了

    首先,观察到题目限制应该不难想到一个容斥。因为思考一下发现这两个限制同时满足难以表达在状态里,因为我们不能对每一道食材都记录它的出现次数。同时还有 至少 这个词汇。那么对谁容斥?

    那应该就是对 不超过一半 的这个限制容斥了。现在让我们枚举一道食材,让它不满足这个限制,也就是说 强制出现次数大于 (frac{n}{2}) 次。

    那我们又可以发现,剩下的食材无论怎么选择都不会再出现第二个不合法的了!

    那么最终的方案数得以轻松表示:用总方案数减掉每一道菜不合法的方案数。

    现在的问题就转化为一个求不合法方案数的问题。

    那么,设 (f[i][j][k]) 表示前 (i) 种方法,选择了 (j) 种,其中有 (k) 种是不合法食材做的菜的搭配方案数。

    那么就会有一个简单的 (dp:)

    [f[i][j][k]=f[i-1][j][k]+f[i-1][j-1][k-1] imes a[i][pos]+f[i-1][j-1][k] imes (sum[i]-a[i][pos]) ]

    其中,(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
    ",All-1);
    	return 0;
    }
    

    这样就有了 (84pts)

    继续考虑,把 (O(n^3m)) 优化到 (O(n^2m)) 就完事了。

    观察一下,我们能不能把 选择多少道菜 和 选了多少道不合法的菜 合并到一起?

    仔细想想,如果我们知道在一种选择方案下,选择的 (pos) 菜品的数量比其他的更多,那么我们实际上无需知道一共选择了多少个,只需要知道这种情况的方案数然后减掉即可。

    那么我们就可以考虑换一个状态:设 (f[i][j]) 表示前 (i) 种食材,选择了 (pos) 食材的食品数量与其他的食材食品数量之差为 (j) 的方案数。

    注意这里的 (j) 可以是负数,所以要整体平移。

    那么就可以同样地写出方程:

    [f[i][j]=f[i-1][j]+a[i][pos] imes f[i-1][j-1]+f[i-1][j+1] imes (sum[i]-a[i][pos]) ]

    初始值也是一样的,而这里注意一下减的时候要从 (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:
    ",P);
    	for(int i=0;i<=n;++i)
    		for(int j=-n;j<=n;++j)
    			printf("%d%c",f[i][getpos(j)],j==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
    ",All-1);
    	return 0;
    }
    
  • 相关阅读:
    windows下Yarn安装与使用(两种方法)
    git配置公钥---解决码云出现git@gitee.com: Permission denied (publickey)
    npm使用国内镜像的两种方法
    【LeetCode】33. Search in Rotated Sorted Array (4 solutions)
    【LeetCode】83. Remove Duplicates from Sorted List
    【LeetCode】82. Remove Duplicates from Sorted List II
    【LeetCode】85. Maximal Rectangle
    【LeetCode】84. Largest Rectangle in Histogram
    【LeetCode】87. Scramble String
    【LeetCode】162. Find Peak Element (3 solutions)
  • 原文地址:https://www.cnblogs.com/h-lka/p/15351105.html
Copyright © 2011-2022 走看看