【洛谷5664】Emiya 家今天的饭(容斥+DP)

点此看题面

  • (n)种烹饪方法和(m)种食材,用第(i)种烹饪方法烹饪第(j)种食材有(a_{i,j})种方法。
  • 现在可以做任意道菜(假设做了(k)道),满足:
    • 至少一道菜。
    • 每道菜烹饪方法各不相同。
    • 一种食材至多出现在(lfloorfrac k2 floor)道菜中。
  • (nle100,mle2 imes10^3)

去年联赛的题目。。。

仔细研究了一下去年考场上的代码,发现和我现在的想法差不多,看来实力并没有太大退步啊。

于是开开心心地来补博客啦!

容斥

发现一种食材至多出现(lfloorfrac k2 floor)中,那么如果我们枚举一种不符合限制(超过(lfloorfrac k2 floor))的食材,显然不可能有大于等于两种食材超过限制。

总方案数随便(DP)一下就好了。

至于不合法方案,枚举一种食材之后,设(g_{i,p})表示考虑了前(i)种烹饪方法,当前枚举食材与其他食材数量之差为(p)的方案数。

转移时就分选择当前食材、选择其他食材两种,最后减去(p>0)的贡献就好了。

代码:(O(n^2m))

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100
#define M 2000
#define X 998244353
#define Inc(x,y) ((x+=(y))>=X&&(x-=X))
using namespace std;
int n,m,a[N+5][M+5],s[N+5],f[N+5][N+5],g[N+5][2*N+5];
I void read(int& x) {char c;x=0;W(!isdigit(c=getchar()));W(x=x*10+(c&15),isdigit(c=getchar()));}
int main()
{
	RI i,j,k,p,q,ans=0;read(n),read(m);
	for(i=1;i<=n;++i) for(j=1;j<=m;++j) read(a[i][j]),Inc(s[i],a[i][j]);//统计每种烹饪方法各食材方案总数
	for(f[0][0]=1,i=1;i<=n;++i) for(j=0;j<=n;++j) f[i][j]=(1LL*f[i-1][j-1]*s[i]+f[i-1][j])%X;//转移类似组合数
	for(i=1;i<=n;++i) Inc(ans,f[n][i]);//总方案数
	for(j=1;j<=m;++j)//枚举不符合限制的食材
	{
		for(g[0][n]=i=1;i<=n;++i) for(p=n/2;p<=2*n;++p)//数组下标不能为负,第二维统一加n
			g[i][p]=(1LL*g[i-1][p-1]*a[i][j]%X+1LL*g[i-1][p+1]*(s[i]-a[i][j]+X)%X+g[i-1][p])%X;//选择当前枚举食材;选择其他食材
		for(p=n+1;p<=2*n;++p) Inc(ans,X-g[n][p]);//减去p>0的贡献
	}
	return printf("%d",ans),0;//输出答案
}
原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu5664.html