csp2019 Emiya 家今天的饭

博客介绍了Luogu题号P5664的矩阵选择问题,讨论如何在限制条件下求解所有可行方案的数乘积之和,并给出了O(mn^2)时间复杂度的优化算法和代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接:https://www.luogu.com.cn/problem/P5664
去年考场上我很菜。。。
现在还是一样菜。。。。。。
题目抽象出来就是给你一个 n n n m m m列的矩阵(数表),可以从里面选出任意个数,每一行只能选一个数,要求每一列选出的数不超过选出总数的 ⌊ 1 2 ⌋ \lfloor \frac{1}{2} \rfloor 21,每一个方案的贡献是所有选出的数的乘积,求所有方案之和取模

如果要直接保证每一列选出的数不超过选出总数的 ⌊ 1 2 ⌋ \lfloor \frac{1}{2} \rfloor 21,一时想不出,可以考虑进行容斥,因为每个方案只有可能有一列选出的数超过。

枚举每一列为超出总数 ⌊ 1 2 ⌋ \lfloor \frac{1}{2} \rfloor 21的列,同时设 d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k]为前 i i i行选出 j j j个该列的数,总共选出了 k k k个数的方案的值, s [ i ] s[i] s[i]为第 i i i行值的和,该列为第 q q q
d p [ i ] [ j ] [ k ] = d p [ i − 1 ] [ j ] [ k ] + d p [ i − 1 ] [ j − 1 ] [ k − 1 ] ∗ a [ i ] [ q ] + d p [ i − 1 ] [ j ] [ k − 1 ] ∗ ( s [ i ] − a [ i ] [ q ] ) dp[i][j][k]=dp[i-1][j][k]+dp[i-1][j-1][k-1]*a[i][q]+dp[i-1][j][k-1]*(s[i]-a[i][q]) dp[i][j][k]=dp[i1][j][k]+dp[i1][j1][k1]a[i][q]+dp[i1][j][k1](s[i]a[i][q])
但这样总的时间复杂度是 O ( m n 3 ) O(mn^3) O(mn3)

还要优化状态,我们发现,我们其实只要考虑第 q q q行和其他行的相对数量,就可以重设状态 d p [ i ] [ j ] dp[i][j] dp[i][j]表示前 i i i行第 q q q列比其他列的数量之和多 j j j的方案的值
则: d p [ i ] [ j ] = d p [ i − 1 ] [ j ] + d p [ i − 1 ] [ j − 1 ] ∗ a [ i ] [ q ] + d p [ i ] [ j + 1 ] ∗ ( s [ i ] − a [ i ] [ q ] ) dp[i][j]=dp[i-1][j]+dp[i-1][j-1]*a[i][q]+dp[i][j+1]*(s[i]-a[i][q]) dp[i][j]=dp[i1][j]+dp[i1][j1]a[i][q]+dp[i][j+1](s[i]a[i][q])

至于算所有满足除第三个条件的方案的值,简单 d p dp dp即可
总时间复杂度 O ( m n 2 ) O(mn^2) O(mn2)

C o d e Code Code

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int MAXN=200,MAXM=2000,Mod=998244353;
int dp[MAXN+10][MAXN+10],g[MAXN+10][MAXM+10];
int a[MAXN+10][MAXM+10],s[MAXN+10];
inline int read();

signed main(){
	//freopen ("std.in","r",stdin);
	//freopen ("std.out","w",stdout);
	int n,m;
	n=read(),m=read();
	for (register int i=1;i<=n;++i)
		for (register int j=1;j<=m;++j)
			a[i][j]=read(),s[i]=(s[i]+a[i][j])%Mod;
	int ans=0;
	for (register int i=0;i<n;++i)	g[i][0]=1;
	for (register int i=1;i<=n;++i)
		for (register int j=1;j<=n;++j)
			g[i][j]=(g[i-1][j-1]*s[i] + g[i-1][j])%Mod;
	for (register int i=1;i<=n;++i)	ans+=g[n][i];
	for (register int k=1;k<=m;++k){
		memset(dp,0,sizeof(dp));
		dp[0][n]=1;
		for (register int i=1;i<=n;++i)
			for (register int j=n-i;j<=n+i;++j)
				dp[i][j]=(dp[i-1][j]+dp[i-1][j-1]*a[i][k]+dp[i-1][j+1]*(s[i]-a[i][k]))%Mod;
		for (register int i=n+1;i<=n+n;++i)	ans-=dp[n][i];
	}
	printf("%lld\n",(ans%Mod+Mod)%Mod);
	return 0;
}

inline int read(){
	int x=0;
	char c=getchar();
	while (!isdigit(c))c=getchar();
	while (isdigit(c))x=(x<<1)+(x<<3)+(c&15),c=getchar();
	return x;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值