Description:
有一个NN行列的棋盘,即该棋盘被分为N∗MN∗M格。现在向棋盘中放棋子,每个格子中最多放一个棋子,也可以一个不放。放完棋子后需要满足如下要求:
1)1)对于第i行来说,其从左往右的前left[i]left[i] 个格子(即最左侧的left[i]left[i] 个连续的格子)中恰好一共有11个棋子;
对于第i行来说,其从右往左的前right[i]right[i]个格子(即最右侧的right[i]right[i]个连续的格子)中恰好一共有11个棋子;
对于每一列来说,这一列上的所有格子内含有的棋子数不得超过1个1个。
其中,1)1)与2)2)条件中,对所有ii满足,即两个区间不会相交。
问,符合上述条件情况下棋子的不同放法一共有多少种?输出放法的个数 mod1,000,000,007mod1,000,000,007后的结果。
Solution:
由于同时有leftleft和rightright,不能同时进行dpdp,否则复杂度爆炸。那么考虑满足所有leftleft的方案,那么在每个leftleft结束前都要选完。设dp[i][j][k]dp[i][j][k]表示选到第ii列,有列空着,还有kk个没选。先计算每列有多少个leftleft结束点,rightright起始点,和空着的点。那么考虑加入新的一列,这列里的leftleft结束点必须选完,那么就是在空着的列中放置这些leftleft。乘上排列即可。新加入的列也可以放置其他两种格子,分别计算即可。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 205, P = 1e9 + 7;
int n, m;
long long fac[maxn], C[maxn][maxn], a[maxn], b[maxn], c[maxn], l[maxn], r[maxn], dp[maxn][maxn][maxn];
void upd(long long &x, long long y) {
x = (x + y) % P;
}
int main() {
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; ++i) {
scanf("%d%d", &l[i], &r[i]);
for(int j = 1; j <= m; ++j) {
if(j > l[i] && j <= m - r[i]) {
++a[j];
}
if(j == l[i]) {
++b[j];
}
if(j == m - r[i] + 1) {
++c[j];
}
}
}
C[0][0] = 1;
for(int i = 1; i <= m; ++i) {
C[i][0] = 1;
for(int j = 1; j <= i; ++j) {
C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % P;
}
}
fac[0] = 1;
for(int i = 1; i <= m; ++i) {
fac[i] = 1LL * fac[i - 1] * i % P;
}
dp[0][0][0] = 1;
for(int i = 0; i < m; ++i) {
for(int j = 0; j <= i; ++j) {
for(int k = 0; k <= n; ++k) {
if(dp[i][j][k]) {
if(j + 1 >= b[i + 1]) {
upd(dp[i + 1][j + 1 - b[i + 1]][k + c[i + 1]], dp[i][j][k] * C[j + 1][b[i + 1]] % P * fac[b[i + 1]] % P);
}
if(j >= b[i + 1]) {
upd(dp[i + 1][j - b[i + 1]][k + c[i + 1]], dp[i][j][k] * a[i + 1] % P * C[j][b[i + 1]] % P * fac[b[i + 1]] % P);
if(k + c[i + 1]) {
upd(dp[i + 1][j - b[i + 1]][k + c[i + 1] - 1], dp[i][j][k] * (k + c[i + 1]) % P * C[j][b[i + 1]] % P * fac[b[i + 1]] % P);
}
}
}
}
}
}
long long ans = 0;
for(int i = 0; i <= m; ++i) {
upd(ans, dp[m][i][0]);
}
printf("%lld\n", ans);
return 0;
}