题目意思是给一个m*n的01矩阵,将剩余的n-m行01矩阵填满,并保证最后的n*n的矩阵每一行和每一列都有且仅有两个1,问有多少种填法。
对于每一行,我们都可以从上一行的所有状态中推出当前这一行填01的方法种数。对于填哪两列其实我们并不用关心,只需要记录到当前行由几个列选了0个1和1个1即可。这样配合组合数就可以进行O(n3)的dp转移。
但是实际上我们知道了当前行有多少个“0”(即0个1),我们便可以求出有多少个“1”,因为01矩阵1的总个数是确定的。这样dp的时间复杂度就变成了O(n2).
#include <bits/stdc++.h>
using namespace std;
int n, m, mod;
typedef long long ll;
const int maxn = 1000 + 5;
char s[maxn][maxn];
ll d[2][500+5][500+5];
ll C[maxn][maxn];
int main(int argc, char const *argv[])
{
scanf("%d%d%d", &n, &m ,&mod);
C[1][0] = C[1][1] = 1;
for(int i = 2; i < 600; ++i) {
C[i][0] = 1;
for(int j = 1; j < 600; ++j) {
C[i][j] = (C[i-1][j]+C[i-1][j-1])%mod;
}
}
for(int i = 1; i <= m; ++i) {
scanf("%s", s[i] + 1);
}
int a = 0 ,b = 0, c = 0;
for(int j = 1; j <= n; ++j) {
int cnt = 0;
for(int i = 1; i <= m; ++i) {
if(s[i][j] == '1') cnt++;
}
if(cnt == 0) a++;
else if(cnt == 1) b++;
else c++;
}
d[0][a][b] = 1;
int x = 0;
for(int i = m+1; i <= n; ++i) {
x ^= 1;
for(int j = 0; j <= n; ++j) {
int k = ((n-i+1) << 1) - (j << 1);
if(k < 0 || k > n) continue;
if(j >= 2){
(d[x][j-2][k+2] += d[x^1][j][k] * C[j][2] % mod) %= mod;
}
if(j >= 1 && k >= 1) {
(d[x][j-1][k] += d[x^1][j][k] * j * k % mod) %= mod;
}
if(k >= 2){
(d[x][j][k-2] += d[x^1][j][k] * C[k][2] % mod) %= mod;
}
}
}
cout << d[x][0][0] << endl;
return 0;
}