P5664 [CSP-S2019] Emiya 家今天的饭

题目描述

给出一个矩阵 要求每横行最多选一个点 每列选的点不超过总结点数一半(向下取整)
再给出每个节点选取的方案数,求总方案数

题解

直接考虑dp
为什么我们要容斥呢 因为既然有列的限制 满足条件的列可以有许多 但是违法的列只能有一个
因为这个违法的列要选出多于一半的点 即使另外所有点都在同一列也没用
如果我们只计算有解的部分,在设计dp状态的时候 无法考虑到加上前面的选择是否非法

接下来枚举不合法的这一列 设计一个简单的状态
首先枚举不合法的这一列 \(col\)
\(f[i][j][k]\) 表示前 \(i\) 行在 \(col\) 这一列选择了了 \(j\) 个点 其它列选择了 \(k\) 个点
有多少种方案
\(s\) 表示第 \(i\)\(a[i][j]\) 的总和
推出方程:

\[f[i][j][k]=f[i-1][j][k-1]*(s[i]-a[i][col]) + f[i-1][j-1][k]*a[i][col] + f[i-1][j][k] \]

分别表示在第 \(i\) 行不选 \(col\) 选择 \(col\) 以及什么都不选
这一步复杂度是 \(O(mn^3)\) 然后考虑如何统计答案
不合法方案数为

\[sum=f[n][j][k] , j>k \]

然后随便算一下总方案数
\(g[i][j]\) 表示前 \(i\) 行选择 \(j\) 个的方案数
则有

\[g[i][j]=g[i-1][j]+g[i-1][j-1]*s[i] \]

然后所有\(g[n][j]\)就是总方案数
最后我们获得了 \(84\) 分的好成绩
说实在的 考场上能推出这些个人就满足了
接着想想下一步怎么优化
对于一个状态 \(f[i][j][k]\)我们发现只统计了 \(j>k\) 时候的答案
也就是说 我们没必要存储下 \(j,k\) 的具体值
只需要记录 \(j\)\(k\) 大多少即可
所以现在 \(f[i][j]\) 表示在前 \(i\) 行中
当前这列比剩下列多选j个有多少种方案

\[f[i][j]=f[i-1][j]+f[i-1][j-1]*a[i][col]+f[i-1][j+1]*(s[i]-a[i][col]) \]

#include<bits/stdc++.h>
#define ll long long
#define inf 0x7fffffff
using namespace std;
int n,m;
#define maxm 2009
#define maxn  109
ll a[maxn][maxm];
#define mod 998244353
ll f[maxn][maxn*2];
ll g[maxn][maxn];
ll s[maxn];
ll ans=0;
signed main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		s[i]=0;
		for(int j=1;j<=m;j++)
		{
			scanf("%lld",&a[i][j]);
			s[i]=(s[i]+a[i][j])%mod;
		}
	}
	for(int col=1;col<=m;col++)
	{
		memset(f,0,sizeof(f));
		f[0][n]=1;//初始状态 
		for(int i=1;i<=n;i++)
		{
			for(int j=n-i;j<=n+i;j++)
			{
				f[i][j]=(f[i-1][j]+f[i-1][j-1]*a[i][col]+(f[i-1][j+1]*(s[i]-a[i][col])%mod))%mod;
			}
		}
		for(int j=1;j<=n;j++)
		{
			ans=(ans+f[n][j+n])%mod;
		}
	}
	g[0][0]=1;
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<=n;j++)
		{
			g[i][j]=g[i-1][j];
			if(j>0)g[i][j]=(g[i][j]+g[i-1][j-1]*s[i]%mod)%mod;
		}
	}
	for(int i=1;i<=n;i++)
	{
		ans=(ans-g[n][i]+mod)%mod;
	}
	printf("%lld\n",(mod-ans+mod)%mod);
	return 0;
}
posted @ 2021-09-17 16:08  lzylzy/kk  阅读(58)  评论(0编辑  收藏  举报
浏览器标题切换
浏览器标题切换end