一个子树内的 前序遍历序 是连续的,
于是可以设 表示以 为 根 的子树, 子树末节点编号为 时的方案数,
.
考虑怎么满足题目的限制条件, 设左子树的 中序遍历 编号为 , 右子树为 , 根节点为 ,
中序遍历 的要求是: , , .
由于 前序遍历 编号是连续的, 所以可以考虑使用 二维矩阵前缀和 判断 转移是否合法 .
:
若存在限制 , 则令矩阵数组 , 表示 中序遍历 中 的编号 要 小于 的 编号,
于是对于前面提到的三个限制, 只需要
- 判断 左上角 右下角 的矩阵是否含有 .
- 判断 左上角 右下角 的矩阵是否含有 .
- 判断 左上角 右下角 的矩阵是否含有 .
若上方有一个矩阵含有 , 说明状态转移不合法, 不予以转移 .
使用递归实现 .
#include<bits/stdc++.h>
#define reg register
int read(){
char c;
int s = 0, flag = 1;
while((c=getchar()) && !isdigit(c))
if(c == '-'){ flag = -1, c = getchar(); break ; }
while(isdigit(c)) s = s*10 + c-'0', c = getchar();
return s * flag;
}
const int maxn = 305;
const int mod = 1e9 + 7;
int N;
int M;
int a[maxn][maxn];
int F[maxn][maxn];
int calc(int x1, int y1, int x2, int y2){ return a[x2][y2] - a[x2][y1-1] - a[x1-1][y2] + a[x1-1][y1-1]; }
int solve(int i, int j){
if(i >= j) return 1;
if(~F[i][j]) return F[i][j];
int res = 0;
for(reg int k = i; k <= j; k ++){
if(calc(i, i+1, i, k)) continue ;
if(calc(k+1, i, j, i)) continue ;
if(calc(k+1, i+1, j, k)) continue ;
res = (res + (1ll*solve(i+1, k)*solve(k+1, j)%mod)) % mod;
}
return F[i][j] = res;
}
void Work(){
N = read(), M = read();
memset(a, 0, sizeof a);
memset(F, -1, sizeof F);
for(reg int i = 1; i <= M; i ++){
int u = read(), v = read();
if(u == v){ printf("0
"); return ; }
a[u][v] = 1;
}
for(reg int i = 1; i <= N; i ++)
for(reg int j = 1; j <= N; j ++) a[i][j] += a[i-1][j] + a[i][j-1] - a[i-1][j-1];
printf("%d
", solve(1, N));
}
int main(){
int T = read();
while(T --) Work();
return 0;
}