题目大意
把1~n顺序入栈,你可以决定出栈序列。
有m组关系,每组关系限制j要在k之前出栈。
DP
首先有个坑点请先判掉,就是存在限制j=k就输出0。。
我们考虑入栈出栈序列。
1入栈,写一个1。
2入栈,写一个2。
3入栈,写一个3。
3出栈,写一个3。
类似这样可以写出一个进出栈序列。
如果我们在最前和最后都加1个0,还可以看做是一个括号序列,也就是一颗以0为根节点的树。
这颗树有以下几个特点:
1、若节点i的子树大小为k,则子树i中的点的编号是[i,i+k-1]。
2、如果i是j的祖先,说明j比i先出栈。
这样的话限制可以转化成子树大小的上下界限制。
我们设f[i,j]表示i子树大小为j,且每一个点都满足限制的方案数。
暂时先不考虑i的限制。
初始f[i,1]=1。
那么有
f[i,j]=∑j−1k=1f[i,j−k]∗f[i+j−k][k]
做完这个dp后,我们把非法的子树大小j都来个f[i,j]=0。
最后输出f[0,n+1]即可。
#include<cstdio>
#include<algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
typedef long long ll;
const int maxn=600+10,mo=1000000007;
int f[maxn][maxn],L[maxn],R[maxn];
int i,j,k,l,t,n,m,ca;
bool czy;
int main(){
scanf("%d",&ca);
while (ca--){
scanf("%d%d",&n,&m);
czy=0;
fo(i,0,n) L[i]=0,R[i]=n+1;
fo(i,1,m){
scanf("%d%d",&j,&k);
if (j==k) czy=1;
if (j<k) R[j]=min(R[j],k-j);
else L[k]=max(L[k],j-k+1);
}
if (czy){
printf("0\n");
continue;
}
fd(i,n,0){
fo(j,1,n+1) f[i][j]=0;
f[i][1]=1;
fo(j,2,n-i+1)
fo(k,1,j-1)
f[i][j]=(f[i][j]+(ll)f[i][j-k]*f[i+j-k][k]%mo)%mo;
fo(j,1,L[i]-1) f[i][j]=0;
fo(j,R[i]+1,n+1) f[i][j]=0;
}
printf("%d\n",f[0][n+1]);
}
}