题目描述
给出一个矩阵 要求每横行最多选一个点 每列选的点不超过总结点数一半(向下取整)
再给出每个节点选取的方案数,求总方案数
题解
直接考虑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
",(mod-ans+mod)%mod);
return 0;
}