zoukankan      html  css  js  c++  java
  • CSP2019 Emiya家今天的饭 [DP][容斥]

    Emiya 家今天的饭

    题目描述

    Emiya 是个擅长做菜的高中生,他共掌握 (n) 种烹饪方法,且会使用 (m) 种主要食材做菜。为了方便叙述,我们对烹饪方法从 $1 sim n $编号,对主要食材从 $1 sim m $编号。

    Emiya 做的每道菜都将使用恰好一种烹饪方法与恰好一种主要食材。更具体地,Emiya 会做 (a_{i,j}) 道不同的使用烹饪方法 (i) 和主要食材 (j) 的菜((1 leq i leq n, 1 leq j leq m)),这也意味着 Emiya 总共会做 (sumlimits_{i=1}^{n} sumlimits_{j=1}^{m}a_{i,j})道不同的菜。

    Emiya 今天要准备一桌饭招待 Yazid 和 Rin 这对好朋友,然而三个人对菜的搭配有不同的要求,更具体地,对于一种包含 (k) 道菜的搭配方案而言:
    Emiya 不会让大家饿肚子,所以将做至少一道菜,即 (k geq 1)
    Rin 希望品尝不同烹饪方法做出的菜,因此她要求每道菜的烹饪方法互不相同
    Yazid 不希望品尝太多同一食材做出的菜,因此他要求每种主要食材至多在一半的菜(即 (lfloor frac{k}{2} floor) 道菜)中被使用
    这里的 (lfloor x floor) 为下取整函数,表示不超过 (x) 的最大整数。

    这些要求难不倒 Emiya,但他想知道共有多少种不同的符合要求的搭配方案。两种方案不同,当且仅当存在至少一道菜在一种方案中出现,而不在另一种方案中出现。

    Emiya 找到了你,请你帮他计算,你只需要告诉他符合所有要求的搭配方案数对质数 (998,244,353998,244,353) 取模的结果。

    数据范围

    对于所有测试点,保证 (1 leq nleq100)
    (1 leq m leq 2000,)
    (0leq a_{i,j}lt 998,244,353。)

    Solution

    爆搜二三十分的样子
    考虑容斥,先算出没有限制条件的所有情况,然后减去不合法的情况。
    所有情况:
    (dp1[i][j])表示前(i)种烹饪方法共选了(j)个食材。则有选和不选两种转移。
    (dp1[i][j] = dp1[i - 1][j] + dp1[i - 1][j - 1] * sum[i])
    非法情况:
    对于每一个食品做一遍dp
    考虑状态,因为食品个数小于(lfloor frac{j}{2} floor)是合法,于是就想到把选了的第t个食品的数量和已经选了的烹饪方法的个数加入状态。
    那么(dp2[i][j][k])表示前i行一共选了j种烹饪方法,第t个食品选了k个的方案数
    于是非法的方案数就是(sumlimits_{k > lfloor frac{j}{2} floor}{dp2[i][j][k]})
    转移有三个:
    (dp2[i][j][k] = dp2[i - 1][j][k] + dp2[i - 1][j - 1][k - 1] * a[i][j] + dp2[i - 1][j - 1][k] * (sum[i] - a[i][j]))
    分别是不做菜,做菜选择第t个食品,做菜没选第t个食品
    复杂度(O(mn^3)) 84分
    考虑优化
    其实非法的状况就是某一种食品数量大于其他种类食品的数量和。比如1,2,3三种食品,一共选了3道菜,食品1选了2种,食品2选了1种,2 > 1,该方案非法。
    所以记录d =(非法食品数(k) - 合法食品数(j))代替上面的j,k两维,若d>0则非法。
    (dp2[i][d] = dp2[i - 1][d] +dp2[i - 1][d - 1]* a[i][j] + dp2[i - 1][d + 1]* (sum[i] - a[i][j]))
    (注意这个d可能小于0,为防止下标越界可以通通加上一个大数字)
    然后就AC了

    Code

    #include<bits/stdc++.h>
    using namespace std;
    const long long N = 105,M = 2005,MOD = 998244353;
    long long a[N][M],dp1[N][M],dp2[N][M],sum[N];
    int n,m;
    int main(){
    	cin >> n >> m;
    	for(int i = 1 ; i <= n; i++)
    		for(int j = 1; j <= m; j++){
    			scanf("%d",&a[i][j]);
    			sum[i] += a[i][j];
    			sum[i] %= MOD;
    		}
    	dp1[0][0] = 1;
    	for(int i = 1; i <= n; i++){
    		for(int j = 0; j <= i; j++)
    		dp1[i][j] = (dp1[i - 1][j] + dp1[i - 1][j - 1] * sum[i]) % MOD; 
    	}
    	long long b = 0;
    	for(int k = 1; k <= m; k++){
    		memset(dp2,0,sizeof(dp2));
    		dp2[0][100] = 1;
    		for(int i = 1; i <= n; i++){
    			for(int d = i - 100; d <= i + 100; d++){
    				dp2[i][d + 100] = (dp2[i - 1][d + 100] + dp2[i - 1][d - 1 + 100] * a[i][k] + dp2[i - 1][d + 1 + 100] * (sum[i] - a[i][k])) % MOD; 
    			}
    		}
    		for(int i = 101;i <= 100 + n; i++){
    			b += dp2[n][i];
    			b %= MOD;
    		}
    	}
    	long long ans = 0;
    	for(int i = 1; i <= M; i++)
    		ans = ans + dp1[n][i];
    	cout << (ans - b) % MOD << endl;
    	return 0;
    } 
    
    
    

    借鉴了这位dalao的题解:https://blog.csdn.net/weixin_37517391/article/details/103110646

  • 相关阅读:
    iOS App之间跳转
    iOS 编码转换
    iOS文件类型判断
    iOS 运行时
    libqrencode生成二维码
    设置app的启动图
    根据字体计算CGRect
    iOS 英文学习
    libev 中IO事件循环解析
    libev 默认事件循环初始化的解析
  • 原文地址:https://www.cnblogs.com/FoxC/p/11924617.html
Copyright © 2011-2022 走看看