【CSP-S 2019】【洛谷P5664】Emiya 家今天的饭

题目

题目链接:https://www.luogu.org/problem/P5664

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 总共会做 \(\sum\limits_{i=1}^{n} \sum\limits_{j=1}^{m} a_{i,j}\) 道不同的菜。

Emiya 今天要准备一桌饭招待 Yazid 和 Rin 这对好朋友,然而三个人对菜的搭配有不同的要求,更具体地,对于一种包含 \(k\) 道菜的搭配方案而言:

  • Emiya 不会让大家饿肚子,所以将做至少一道菜,即 \(k \geq 1\)
  • Rin 希望品尝不同烹饪方法做出的菜,因此她要求每道菜的烹饪方法互不相同
  • Yazid 不希望品尝太多同一食材做出的菜,因此他要求每种主要食材至多在一半的菜(即 \(\lfloor \frac{k}{2} \rfloor\) 道菜)中被使用

这里的 \(\lfloor x \rfloor\) 为下取整函数,表示不超过 \(x\) 的最大整数。

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

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

思路:

我菜死了。
其实这道题可以转换为一个\(n\times m\)的矩阵,矩阵每一行最多只能选一个数,每一列可以选若干个数,询问有多少种方案满足选取的每一列的个数均不超过选取总个数的一半。
正难则反。我们发现,对于不合法的方案,仅有一列选取的个数会超过总个数的一半。
所以我们可以枚举超过总个数一半的那一列,然后设\(f[i][j][k]\)表示选取到第\(i\)行,枚举的超过一半的这一列选择了\(j\)个,其他列选择了\(k\)个的方案数。
那么这一列可以选择这一列,可以选择其他列,也可以不选。所以有方程

\[f[i][j][k]=f[i-1][j][k]+f[i-1][j-1][k]\times cnt[i][j]+f[i-1][j][k-1]\times (sum[i]-cnt[i][j]) \]

其中\(sum[i]=\sum^m_{j=1}cnt[i][j]\)
那么选取枚举的这一行超过一半,最终不合法的方案数就是

\[ans=\sum_{j>k}f[n][j][k] \]

时间复杂度\(O(mn^3)\),这样就得到了\(84pts\)的超级足的暴力分。
然而我考场这都没写出来qwq
我们发现最终只要满足\(j>k\)就可以计算进答案,中间过程我们也只关心\(j,k\)的大小关系。
所以我们可以省去\(j,k\)那两维,设\(f[i][j]\)表示第\(i\)行,选择超过一半的这一列比其他列多选的个数为\(j\)的方案数。
那么就有方程

\[f[i][k]=f[i-1][k]+f[i-1][k-1]\times cnt[i][j]+(sum[i]-cnt[i][j])\times f[i-1][k+1] \]

然后答案就是

\[ans=\sum^{n}_{i=1}f[n][i] \]

这样我们就在\(O(n^2)\)的时间复杂度内求出了不合法的方案数。
总方案数很好计算,第\(i\)行有\(sum[i]\)种选择方案,再加上不选,所以总方案数就是

\[all=\Pi^{n}_{i=1}(sum[i]+1) \]

注意还要排除全部不选的情况。所以最终答案就是\(all-ans-1\)
时间复杂度\(O(mn^2)\)

代码:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;

const int N=110,M=2010,MOD=998244353;
ll ans,all,f[N][N+N],cnt[N][M],sum[N];
int n,m;

int main()
{
	scanf("%d%d",&n,&m);
	all=1;
	for (int i=1;i<=n;i++)
	{
		for (int j=1;j<=m;j++)
		{
			scanf("%lld",&cnt[i][j]);
			sum[i]+=cnt[i][j];
		}
		all=((sum[i]+1)%MOD*all)%MOD;
	}
	for (int j=1;j<=m;j++)
	{
		f[0][N]=1;
		for (int i=1;i<=n;i++)
			for (int k=-n+N;k<=n+N;k++)
				f[i][k]=(f[i-1][k]+f[i-1][k-1]*cnt[i][j]+(sum[i]-cnt[i][j])%MOD*f[i-1][k+1])%MOD;
		for (int i=1;i<=n;i++)
			ans=(ans+f[n][i+N])%MOD;
	}
	printf("%lld",((all-ans-1)%MOD+MOD)%MOD);
	return 0;
}
posted @ 2019-12-21 15:14  stoorz  阅读(228)  评论(0编辑  收藏  举报