原题传送门
每行最多取一个告诉我们可以枚举行
所以这道题目总体复杂度里肯定有行的复杂度 O ( n ) O(n) O(n)
考场上写的是 m = 2 / 3 m=2/3 m=2/3的暴力,直接把每一列分别取了几个写到状态里面去
正解需要考虑正难则反
我们写直接将每行最多一个当作大前提
要求的是没有一列取了超过一半的东西
那么这个答案其实 =所有方案-有一列取了超过一半的方案
这里就有一个重要性质:最多只有一列会超过一半(虽然显然但是我考场上并没发现)
令
s i s_i si表示第 i i i行的和
g i , j g_{i,j} gi,j表示前 i i i行选了 j j j个的方案数
f i , j f_{i,j} fi,j表示前 i i i行,超过一半的列比其他所有多 j j j个的方案数
先求 g i , j g_{i,j} gi,j
g i , j = g i − 1 , j + s i ∗ g i − 1 , j − 1 g_{i,j}=g_{i-1,j}+s_i*g_{i-1,j-1} gi,j=gi−1,j+si∗gi−1,j−1
再求 f i , j f_{i,j} fi,j
我这个状态的设计已经将超过一半的情况表示了出来,但是并没有表示超过的一半的列是哪一列
但是我们可以枚举超过一半的列,再具体求 f f f,因为枚举的复杂度是 O ( m ) O(m) O(m),求值的复杂度是 O ( n m ) O(nm) O(nm),所以,求 f f f数组的值为本题主要复杂度, O ( n 2 m ) O(n^2m) O(n2m)
令第 k k k列选择的东西超过了一半
f i , j = f i − 1 , j + a i , k ∗ f i − 1 , j − 1 + ( s i − a i , k ) ∗ f i − 1 , j + 1 f_{i,j}=f_{i-1,j}+a_{i,k}*f_{i-1,j-1}+(s_i-a_{i,k})*f_{i-1,j+1} fi,j=fi−1,j+ai,k∗fi−1,j−1+(si−ai,k)∗fi−1,j+1
答案为 ∑ i = 1 n f n , i \sum_{i=1}^{n}f_{n,i} i=1∑nfn,i
Code:
#include
#define maxn 4010
#define LL long long
using namespace std;
const LL qy = 998244353;
int n, m;
LL ans, s[maxn], f[110][maxn], g[maxn][maxn], a[maxn][maxn];
inline int read(){
int s = 0, w = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') w = -1;
for (; isdigit(c); c = getchar()) s = (s << 1) + (s << 3) + (c ^ 48);
return s * w;
}
int main(){
n = read(), m = read();
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j) (s[i] += a[i][j] = read()) %= qy;
for (int k = 1; k <= m; ++k){
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] + a[i][k] * f[i - 1][j - 1] % qy + (s[i] - a[i][k] + qy) % qy * f[i - 1][j + 1] % qy) % qy;
for (int i = 1; i <= n; ++i) (ans += f[n][n + i]) % qy;
}
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] + (j ? s[i] * g[i - 1][j - 1] % qy : 0)) % qy;
ans = (-ans + qy) % qy;
for (int i = 1; i <= n; ++i) (ans += g[n][i]) %= qy;
printf("%lld\n", ans);
return 0;
}