题目链接: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[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])
但这样总的时间复杂度是
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[i−1][j]+dp[i−1][j−1]∗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;
}